<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <title>HankChan&#39;s Blog</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="你要我写什么描述？">
<meta property="og:type" content="website">
<meta property="og:title" content="HankChan&#39;s Blog">
<meta property="og:url" content="http://hexo.hankchan.cn/index.html">
<meta property="og:site_name" content="HankChan&#39;s Blog">
<meta property="og:description" content="你要我写什么描述？">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="HankChan&#39;s Blog">
<meta name="twitter:description" content="你要我写什么描述？">
  
    <link rel="alternate" href="/atom.xml" title="HankChan&#39;s Blog" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  <link rel="stylesheet" href="/css/style.css">
  

</head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/" id="logo">HankChan&#39;s Blog</a>
      </h1>
      
        <h2 id="subtitle-wrap">
          <a href="/" id="subtitle">生活就像海洋</a>
        </h2>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/">Home</a>
        
          <a class="main-nav-link" href="/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="搜索"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="http://hexo.hankchan.cn"></form>
      </div>
    </div>
  </div>
</header>
      <div class="outer">
        <section id="main">
  
    <article id="post-docker-e5-be-ae-e6-9c-8d-e5-8a-a1-e5-ae-b9-e5-99-a8-e5-8c-96-e5-bc-80-e5-8f-91-e6-8e-a2-e7-b4-a2" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2017/03/13/docker-e5-be-ae-e6-9c-8d-e5-8a-a1-e5-ae-b9-e5-99-a8-e5-8c-96-e5-bc-80-e5-8f-91-e6-8e-a2-e7-b4-a2/" class="article-date">
  <time datetime="2017-03-13T11:02:15.000Z" itemprop="datePublished">2017-03-13</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Docker/">Docker</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2017/03/13/docker-e5-be-ae-e6-9c-8d-e5-8a-a1-e5-ae-b9-e5-99-a8-e5-8c-96-e5-bc-80-e5-8f-91-e6-8e-a2-e7-b4-a2/">Docker-微服务容器化开发探索</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="由于图片有些问题，所以顺便在文章开头先附上原稿的PDF链接："><a href="#由于图片有些问题，所以顺便在文章开头先附上原稿的PDF链接：" class="headerlink" title="由于图片有些问题，所以顺便在文章开头先附上原稿的PDF链接："></a>由于图片有些问题，所以顺便在文章开头先附上原稿的PDF链接：</h2><p> <a href="http://blog.hankchan.cn/wp-content/uploads/2017/03/Docker-微服务容器化开发探索.pdf" target="_blank" rel="external">Docker-微服务容器化开发探索</a></p>
<h1 id="1-Docker与微服务"><a href="#1-Docker与微服务" class="headerlink" title="1.Docker与微服务"></a>1.Docker与微服务</h1><h2 id="1-1-微服务特点"><a href="#1-1-微服务特点" class="headerlink" title="1.1.微服务特点"></a>1.1.微服务特点</h2><h4 id="彼此独立："><a href="#彼此独立：" class="headerlink" title="彼此独立："></a>彼此独立：</h4><p>既然是一个独立的服务，那必然是一个完整的自治系统，不依赖外部的东西就能够提供服务。有自己一整套的完整的运行机制，有和外部通讯的标准化接口。它就是一个独立的服务，可以探测到其他服务彼此的存在。离开了其他个体，一样能够运转，只是功能比较单一。</p>
<h4 id="原子化："><a href="#原子化：" class="headerlink" title="原子化："></a>原子化：</h4><p>作为一个微服务，一定是一个原子化的服务。也就是说服务不能再划分成更小的服务了。世界上的一些事物都是有原子构成的。它为什么能构成所有的物体，正是由于它足够的基础。如果一个服务还能划分成几个小的服务，那我们就不能称之为一个微服务，它其实可以通过几个微服务组合成的一个系统。</p>
<h4 id="组合和重构："><a href="#组合和重构：" class="headerlink" title="组合和重构："></a>组合和重构：</h4><p>如果是最原子的服务，那一定是没有任何用处的。微服务之所以神奇，在于它能快速的组合和重构。彼此组合成一个系统。系统里面所有的实体在概念上是对等的。因此它的结构相对简单化。是一种松散耦合的结构，这样的系统，往往具有更强的可扩展性和鲁棒性。</p>
<h2 id="1-2-Docker的作用"><a href="#1-2-Docker的作用" class="headerlink" title="1.2.Docker的作用"></a>1.2.Docker的作用</h2><p>在Docker出现之前，微服务架构虽然存在，但是实现的技术门槛很高。微服务要运行，首先需要一套执行的环境。这套环境不能对外部有依赖性。同时，执行环境的粒度又必须足够的小，这样才能称之为“微”，否则必然是对资源的巨大浪费。一个微服务可以跑在一台虚拟机上面，但是虚拟机粒度太大，即使最小的虚拟机，也至少也有1个核。服务一个用户的服务，显然用不了一个核。同时，虚拟机有没有一套方便的管理机制，能够快速的让这些服务之间能够组合和重构。Docker出现以后，我们看到了微服务的一个非常完美的运行环境。</p>
<h2 id="1-3-Docker特点"><a href="#1-3-Docker特点" class="headerlink" title="1.3. Docker特点"></a>1.3. Docker特点</h2><h4 id="独立性："><a href="#独立性：" class="headerlink" title="独立性："></a>独立性：</h4><p>一个容器就是一个完整的执行环境，不依赖外部任何的东西。</p>
<h4 id="细粒度："><a href="#细粒度：" class="headerlink" title="细粒度："></a>细粒度：</h4><p>一台物理机器可以同时运行成百上千个容器。其计算粒度足够的小。</p>
<h4 id="快速创建和销毁："><a href="#快速创建和销毁：" class="headerlink" title="快速创建和销毁："></a>快速创建和销毁：</h4><p>容器可以在秒级进行创建和销毁，非常适合服务的快速构建和重组。</p>
<h4 id="完善的管理工具："><a href="#完善的管理工具：" class="headerlink" title="完善的管理工具："></a>完善的管理工具：</h4><p>数量众多的容器编排管理工具，能够快速的实现 服务的组合和调度。</p>
<h2 id="1-4-去中心化的云服务"><a href="#1-4-去中心化的云服务" class="headerlink" title="1.4. 去中心化的云服务"></a>1.4. 去中心化的云服务</h2><p>去中心化的云服务，是一个非常典型的应用场景。什么是去中心化的云服务呢？这里做一个类比，譬如家里的供暖，可以采用集中化的供暖方式。由电厂或者钢铁厂统一提供供暖服务。当然，也有的家庭自己会建设一套中央空调系统进行供暖。云服务，也会有类似的趋势。目前云计算的发展比较低级。主要是以托管为主，因此大部分还是中心化的云服务。随着云计算的应用越来越垂直化，必然也会出现越来越多的去中心化的应用场景。</p>
<p>就拿个人云存储来举例。每个人都有一套个人的云的存储系统。这套系统运行在任意的提供“水和电”的基础云服务商的系统之上。并且可以任意在不同的服务商之间迁移和部署。不同的用户，可能位于不同的服务商之上。完全由自己控制的一套系统。每一套系统，都是一系列微小的服务组合而成。虽然底层也依赖基础云服务商，但是他们的作用更像水和电一样。</p>
<p>借助于这样的服务，用户能够快速的构建一套属于自己的系统。选购他们，就像在商店里面选购商品一样，拿回家，插上电就可以用了。</p>
<h1 id="2-Docker入门"><a href="#2-Docker入门" class="headerlink" title="2. Docker入门"></a>2. Docker入门</h1><h2 id="2-1-Docker基本概念"><a href="#2-1-Docker基本概念" class="headerlink" title="2.1. Docker基本概念"></a>2.1. Docker基本概念</h2><h3 id="2-1-1-镜像（Image）"><a href="#2-1-1-镜像（Image）" class="headerlink" title="2.1.1. 镜像（Image）"></a>2.1.1. 镜像（Image）</h3><p>我们都知道，操作系统分为内核和用户空间。对于Linux而言，内核启动后，会挂载root文件系统为其提供用户空间支持。而Docker镜像，就相当于是一个root文件系统。比如官方镜像ubuntu:14.04就包含了完整的一套Ubuntu 14.04最小系统的root文件系统。</p>
<p>Docker镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。镜像不包含任何动态数据，其内容在构建之后也不会被改变。</p>
<h4 id="关于分层存储"><a href="#关于分层存储" class="headerlink" title="关于分层存储"></a>关于分层存储</h4><p>由于镜像包含操作系统完整的root文件系统，其体积往往很庞大，因此Docker利用UnionFS技术，设计为分层存储的架构。严格意义上说，镜像并非就像是ISO文件一样的打包文件，镜像这个称呼是一个虚拟概念。实际上并非由一个文件组成的，而是由一组多个层次的文件系统组成。</p>
<p>基于分层存储，镜像可以更易于复用、定制。我们可以使用原有的镜像分层，在其基础上进一步添加新的层，定制自己所需要的内容，构建为新的镜像。</p>
<h3 id="2-1-2-容器（Container）"><a href="#2-1-2-容器（Container）" class="headerlink" title="2.1.2. 容器（Container）"></a>2.1.2. 容器（Container）</h3><p>容器的实质是进程，但是与直接在宿主机上执行的进程不同，容器进程运行在属于自己的独立命名空间，所以容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间等等。</p>
<p>举个例子来说，镜像与容器的关系，就像是面向对象语言中的类和实例的关系。容器基于镜像生成，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。</p>
<h4 id="关于分层存储-1"><a href="#关于分层存储-1" class="headerlink" title="关于分层存储"></a>关于分层存储</h4><p>对于容器来说，同样使用的是分层存储，就是在每一个容器运行时，会以其对应的镜像为基础层，在该基础层上创建一个属于当前容器的存储层，这个层次可以成为容器存储层。容器存储层是为容器运行时读写用而准备的。</p>
<p>容器销毁时，容器存储层也会随着销毁。</p>
<h4 id="关于写数据"><a href="#关于写数据" class="headerlink" title="关于写数据"></a>关于写数据</h4><p>按照Docker最佳实践的要求，容器不应该向其存储层内写入任何数据，容器存储层要求保持无状态化。所有文件写入操作，都应该使用数据卷（Volume，其生存周期独立于容器）、或者绑定宿主目录。这样操作会直接对宿主发生读写，其性能和稳定性会更高。</p>
<h4 id="关于容器ID和容器Name"><a href="#关于容器ID和容器Name" class="headerlink" title="关于容器ID和容器Name"></a>关于容器ID和容器Name</h4><p>每一个容器启动后都会自动生成一个唯一的容器ID，是该容器的唯一标识，容器Name可以在创建容器时自定义。</p>
<p>可以通过容器ID或容器Name对容器执行启动、停止、删除等操作。</p>
<h3 id="2-1-3-仓库（Repository）"><a href="#2-1-3-仓库（Repository）" class="headerlink" title="2.1.3. 仓库（Repository）"></a>2.1.3. 仓库（Repository）</h3><p>镜像在构建完成后，可以很容易在当前的宿主机上运行，但是如果需要在其他服务器上运行该镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry就是这样的一个服务。</p>
<p>一个Docker  Registry可以包含多个仓库（Repository）；每个仓库可以包含多个标签（Tag）；每个标签对应了一个镜像。</p>
<p>比如，一个仓库名包含同一个软件的不同版本的镜像，标签就常用于对应软件的版本号。可以通过&lt;仓库名&gt;:&lt;标签&gt;的格式来指定具体是这个软件哪个版本的镜像。Docker默认将latest作为默认的标签。</p>
<p>以运行AliTomcat基础环境的镜像为例，如：alitomcat:1.0，alitomcat是仓库名字，1.0是其版本号。如果忽略1.0，就将被视为：alitomcat:latest。</p>
<p>另外，仓库名又常常以两段式路径形式出现，比如：yangyitech/alitomcat:1.0。其中前缀是用户名，具体形式通常与提供Registry服务的提供商有关。</p>
<h1 id="3-Docker环境搭建"><a href="#3-Docker环境搭建" class="headerlink" title="3. Docker环境搭建"></a>3. Docker环境搭建</h1><h2 id="3-1-基础环境"><a href="#3-1-基础环境" class="headerlink" title="3.1. 基础环境"></a>3.1. 基础环境</h2><p>系统要求：</p>
<p>Docker 最低支持 CentOS 7。（应该是CentOS7对Docker支持更良好）</p>
<p>Docker 需要安装在 64 位的平台，并且内核版本不低于 3.10（使用<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">uname -r</div></pre></td></tr></table></figure></p>
<p>命令查看）。 CentOS-7满足最低内核的要求，但由于内核版本比较低，部分功能（如overlay2存储层驱动）无法使用，并且部分功能可能不太稳定。</p>
<h4 id="现有服务器资源"><a href="#现有服务器资源" class="headerlink" title="现有服务器资源"></a>现有服务器资源</h4><p>阿里云私有云CentOS-7.2 服务器</p>
<p>CPU 8核 内存 16G 数据盘100G</p>
<h4 id="内网IP"><a href="#内网IP" class="headerlink" title="内网IP"></a>内网IP</h4><p>10.153.216.19</p>
<p>10.153.216.13</p>
<h4 id="登陆用户"><a href="#登陆用户" class="headerlink" title="登陆用户"></a>登陆用户</h4><p>root</p>
<h4 id="密码："><a href="#密码：" class="headerlink" title="密码："></a>密码：</h4><p>（略）</p>
<h2 id="3-2-安装Docker服务"><a href="#3-2-安装Docker服务" class="headerlink" title="3.2. 安装Docker服务"></a>3.2. 安装Docker服务</h2><h3 id="第一步：使用官方脚本安装Docker"><a href="#第一步：使用官方脚本安装Docker" class="headerlink" title="第一步：使用官方脚本安装Docker"></a>第一步：使用官方脚本安装Docker</h3><p>Docker官方提供了安装运行脚本，通过运行以下命令可以自动安装Docker运行环境：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# curl -sSL https://get.docker.com/ | sh</div></pre></td></tr></table></figure>
<h3 id="第二步：启动Docker服务"><a href="#第二步：启动Docker服务" class="headerlink" title="第二步：启动Docker服务"></a>第二步：启动Docker服务</h3><p>执行命令启动Docker引擎：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# systemctl enable docker</div><div class="line"></div><div class="line">[root@xxxx]# systemctl start docker</div></pre></td></tr></table></figure>
<h3 id="第三步：建立docker用户组"><a href="#第三步：建立docker用户组" class="headerlink" title="第三步：建立docker用户组"></a>第三步：建立docker用户组</h3><p>默认情况下，docker命令会使用<a href="https://en.wikipedia.org/wiki/Unix_domain_socket" target="_blank" rel="external">Unix socket</a>与Docker引擎通讯。而只有root用户和docker组的用户才可以访问 Docker 引擎的Unix socket。出于安全考虑，一般Linux系统上不会直接使用root用户。因此，更好地做法是将需要使用 docker 的用户加入docker用户组。</p>
<p>首先查看是否存在docker用户组：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# grep docker /etc/group</div></pre></td></tr></table></figure>
<p>如果不存在，首先需要创建docker用户组：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# groupadd docker</div></pre></td></tr></table></figure>
<h3 id="第四步：将非root用户加入group组"><a href="#第四步：将非root用户加入group组" class="headerlink" title="第四步：将非root用户加入group组"></a>第四步：将非root用户加入group组</h3><p>如前面所说，出于安全考虑，不要直接使用root用户。</p>
<p>首先创建一个用户user：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# useradd user</div></pre></td></tr></table></figure>
<p>将用户加入docker组：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# usermod -aG docker user</div></pre></td></tr></table></figure>
<p>使用命令验证Docker：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker -v</div></pre></td></tr></table></figure>
<h3 id="验证通过，安装完毕！"><a href="#验证通过，安装完毕！" class="headerlink" title="验证通过，安装完毕！"></a>验证通过，安装完毕！</h3><h1 id="4-Docker基础操作命令"><a href="#4-Docker基础操作命令" class="headerlink" title="4. Docker基础操作命令"></a>4. Docker基础操作命令</h1><h2 id="4-1-查看本地镜像"><a href="#4-1-查看本地镜像" class="headerlink" title="4.1. 查看本地镜像"></a>4.1. 查看本地镜像</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker images</div></pre></td></tr></table></figure>
<h2 id="4-2-搜索目标镜像"><a href="#4-2-搜索目标镜像" class="headerlink" title="4.2. 搜索目标镜像"></a>4.2. 搜索目标镜像</h2><p>如，搜索tomcat镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker search tomcat</div></pre></td></tr></table></figure>
<h2 id="4-3-安装目标镜像"><a href="#4-3-安装目标镜像" class="headerlink" title="4.3. 安装目标镜像"></a>4.3. 安装目标镜像</h2><p>如，安装官方tomcat镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker pull tomcat</div></pre></td></tr></table></figure>
<p>此处未指定版本号，默认为latest版本。</p>
<h2 id="4-4-镜像导出"><a href="#4-4-镜像导出" class="headerlink" title="4.4. 镜像导出"></a>4.4. 镜像导出</h2><p>保存镜像到一个tar包：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker save ubuntu:latest &amp;gt; ubuntu_save.tar</div></pre></td></tr></table></figure>
<h2 id="4-5-镜像导入"><a href="#4-5-镜像导入" class="headerlink" title="4.5. 镜像导入"></a>4.5. 镜像导入</h2><p>加载一个tar包格式的镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker load &amp;lt; ubuntu_save.tar</div></pre></td></tr></table></figure>
<h2 id="4-6-退出镜像-容器"><a href="#4-6-退出镜像-容器" class="headerlink" title="4.6. 退出镜像/容器"></a>4.6. 退出镜像/容器</h2><p>在使用交互模式进入镜像或者容器的文件系统后，通过以下命令退出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"># exit</div></pre></td></tr></table></figure>
<p>在使用该方式退出容器的同时，容器也将会停止运行。</p>
<h2 id="4-7-查看运行态容器"><a href="#4-7-查看运行态容器" class="headerlink" title="4.7. 查看运行态容器"></a>4.7. 查看运行态容器</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker ps</div></pre></td></tr></table></figure>
<h2 id="4-8-查看最近容器"><a href="#4-8-查看最近容器" class="headerlink" title="4.8. 查看最近容器"></a>4.8. 查看最近容器</h2><p>可以使用以下命令顺序查看最近操作的容器列表：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker ps -l</div></pre></td></tr></table></figure>
<h2 id="4-9-查看所有容器"><a href="#4-9-查看所有容器" class="headerlink" title="4.9. 查看所有容器"></a>4.9. 查看所有容器</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker ps -a</div></pre></td></tr></table></figure>
<h2 id="4-10-启动容器"><a href="#4-10-启动容器" class="headerlink" title="4.10. 启动容器"></a>4.10. 启动容器</h2><p>根据容器名启动运行容器：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker start swift-edas-web</div></pre></td></tr></table></figure>
<p>根据容器ID启动运行容器：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker start 94e2</div></pre></td></tr></table></figure>
<p>在使用容器ID操作容器的时候，不一定需要使用完整的容器ID，只需要前几位能够区别于其他容器ID的字符即可，如94e2就是该容器ID的前4位，足以在所有容器中识别到该容器。</p>
<h2 id="4-11-终止容器"><a href="#4-11-终止容器" class="headerlink" title="4.11. 终止容器"></a>4.11. 终止容器</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker stop swift-edas-web</div></pre></td></tr></table></figure>
<h2 id="4-12-查看容器日志"><a href="#4-12-查看容器日志" class="headerlink" title="4.12. 查看容器日志"></a>4.12. 查看容器日志</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker logs &amp;lt;containerID&amp;gt;</div></pre></td></tr></table></figure>
<h2 id="4-13-容器生成镜像"><a href="#4-13-容器生成镜像" class="headerlink" title="4.13. 容器生成镜像"></a>4.13. 容器生成镜像</h2><p>当需要对镜像或者容器添加层次时候，可以在完成添加后，退出容器，然后通过以下命名生成新的镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker commit &lt;containerID&gt; &lt;New Image Name&gt;</div></pre></td></tr></table></figure>
<h1 id="5-EDAS-HSF应用容器化"><a href="#5-EDAS-HSF应用容器化" class="headerlink" title="5. EDAS-HSF应用容器化"></a>5. EDAS-HSF应用容器化</h1><p>在这里以阿里云EDAS-HSF分布式服务应用swift-edas项目为例子，简单介绍如果实现分布式服务应用的容器化实现。</p>
<h2 id="5-1-搭建Ali-Tomcat镜像"><a href="#5-1-搭建Ali-Tomcat镜像" class="headerlink" title="5.1. 搭建Ali-Tomcat镜像"></a>5.1. 搭建Ali-Tomcat镜像</h2><p>由于EDAS-HSF应用的部署有一个Ali-Tomcat只能部署一个应用的限制，为了达到服务容器化运行的目的，我们基于Ali-Tomcat创建一个应用运行镜像。</p>
<h3 id="第一步：选择基础镜像"><a href="#第一步：选择基础镜像" class="headerlink" title="第一步：选择基础镜像"></a>第一步：选择基础镜像</h3><p>我们在选择CentOS作为基础镜像，从官方镜像仓库拉取CentOS的镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker pull centos</div></pre></td></tr></table></figure>
<p>（未指定版本号，默认为最新版本latest）</p>
<p>拉取成功后，可以查看本地镜像已经存在centos的镜像。</p>
<h3 id="第二步：获取镜像必须的安装包"><a href="#第二步：获取镜像必须的安装包" class="headerlink" title="第二步：获取镜像必须的安装包"></a>第二步：获取镜像必须的安装包</h3><p>镜像创建需要用到的有taobao-tomcat的tgz安装包和java-jdk的rpm安装包。</p>
<p>如果是远程服务器，可以通过rz命令，将本地的文件上传到服务器中。</p>
<p>使用rz命令，需要使用以下命令先安装rz：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# yum install lrzsz -y</div></pre></td></tr></table></figure>
<p>这里我选择在/opt/目录下创建一个文件夹setuppackages作为一些安装包文件的存放路径。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# cd /opt/</div><div class="line"></div><div class="line">[root@xxxx]# mkdir setuppackages</div></pre></td></tr></table></figure>
<p>进入到该目录后通过rz命令将taobao-tomcat.tgz和jdk的安装包上传到服务器上。</p>
<h3 id="第三步：运行基础镜像"><a href="#第三步：运行基础镜像" class="headerlink" title="第三步：运行基础镜像"></a>第三步：运行基础镜像</h3><p>使用交互模式进入CentOS镜像，同时需要在启动时挂载本地安装包所在目录到镜像中：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker run -i -t -v /opt/setuppackages/:/mnt/software/ centos /bin/bash</div></pre></td></tr></table></figure>
<h3 id="第四步：安装JavaJDK"><a href="#第四步：安装JavaJDK" class="headerlink" title="第四步：安装JavaJDK"></a>第四步：安装JavaJDK</h3><p>进入上一步中挂载的目录/mnt/software中，执行安装jdk安装包：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# rpm -ivh jdk-8u121-linux-x64.rpm</div></pre></td></tr></table></figure>
<p>rpm安装方式的JDK默认安装在/usr/java目录下。</p>
<p>使用<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]ls /usr/java/</div></pre></td></tr></table></figure></p>
<p>命令获取JavaHome全路径，这里是/usr/java/jdk1.8.0_121</p>
<p>配置JDK环境变量：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# vi /etc/profile</div></pre></td></tr></table></figure>
<p>在该文本最后加入如下内容：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">export JAVA_HOME=/usr/java/jdk1.8.0_121</div><div class="line"></div><div class="line">export JRE_HOME=/usr/java/jdk1.8.0_121/jre</div><div class="line"></div><div class="line">export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin</div><div class="line"></div><div class="line">export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib</div></pre></td></tr></table></figure></p>
<p>保存退出当前文件。</p>
<p>使改变生效：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# source /etc/profile</div></pre></td></tr></table></figure>
<h3 id="第五步：安装Ali-Tomcat"><a href="#第五步：安装Ali-Tomcat" class="headerlink" title="第五步：安装Ali-Tomcat"></a>第五步：安装Ali-Tomcat</h3><p>首先复制taobao-tomcat安装包到/usr/local目录下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# cp taobao-tomcat-7.0.59.tgz /usr/local</div></pre></td></tr></table></figure>
<p>进入/usr/local并解压：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# cd /usr/local</div><div class="line"></div><div class="line">[root@xxxx]# tar -zxvf taobao-tomcat-7.0.59.tgz</div><div class="line"></div><div class="line">[root@xxxx]# rm taobao-tomcat-7.0.59.tgz</div></pre></td></tr></table></figure>
<p>为taobao-tomcat安装根目录更改名字为alitomcat：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# mv taobao-tomcat-7.0.59 alitomcat</div></pre></td></tr></table></figure>
<h3 id="第六步：配置运行环境变量"><a href="#第六步：配置运行环境变量" class="headerlink" title="第六步：配置运行环境变量"></a>第六步：配置运行环境变量</h3><p>设置环境变量：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# vi ~/.bashrc</div></pre></td></tr></table></figure>
<p>加入以下内容，保存退出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">export JAVA_HOME=/usr/java/jdk1.8.0_121</div><div class="line"></div><div class="line">export PATH=$PATH:$JAVA_HOME/bin</div></pre></td></tr></table></figure>
<p>使修改生效：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]## source ~/.bashrc</div></pre></td></tr></table></figure>
<h3 id="第七步：编写运行脚本"><a href="#第七步：编写运行脚本" class="headerlink" title="第七步：编写运行脚本"></a>第七步：编写运行脚本</h3><p>创建脚本文件：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# touch /root/run.sh</div><div class="line"></div><div class="line">[root@xxxx]# vi /root/run.sh</div></pre></td></tr></table></figure>
<p>加入内容如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">#!/bin/bash</div><div class="line"></div><div class="line">source ~/.bashrc</div><div class="line"></div><div class="line">sh /usr/local/alitomcat/bin/catalina.sh run</div></pre></td></tr></table></figure>
<p>为脚本添加执行权限：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# chmod u+x /root/run.sh</div></pre></td></tr></table></figure>
<h3 id="第八步：退出当前镜像"><a href="#第八步：退出当前镜像" class="headerlink" title="第八步：退出当前镜像"></a>第八步：退出当前镜像</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# exit</div></pre></td></tr></table></figure>
<h3 id="第九步：创建新镜像"><a href="#第九步：创建新镜像" class="headerlink" title="第九步：创建新镜像"></a>第九步：创建新镜像</h3><p>获取刚刚运行的容器ID：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker ps -a</div></pre></td></tr></table></figure>
<p>可以看到最近的一个容器ID，如：a78d9b71d4e8</p>
<p>创建镜像：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker commit a78d alitomcat:0.2</div></pre></td></tr></table></figure>
<p>查看镜像库：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker images</div></pre></td></tr></table></figure>
<p>可以看到已经存在创建成功的alitomcat:0.2镜像。</p>
<h2 id="5-2-应用部署"><a href="#5-2-应用部署" class="headerlink" title="5.2. 应用部署"></a>5.2. 应用部署</h2><p>我们选择两个web应用edas-service和edas-web的war包用于部署。</p>
<p>在部署过程中，我们使用挂载数据卷方式将应用war包挂载到镜像中的alitomcat应用部署目录。</p>
<h3 id="第一步：创建应用部署目录"><a href="#第一步：创建应用部署目录" class="headerlink" title="第一步：创建应用部署目录"></a>第一步：创建应用部署目录</h3><p>在/opt/目录下创建一个目录software用于挂载部署目录：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# mkdir software</div></pre></td></tr></table></figure>
<p>另外，需要进入software目录，分别创建用于存放edas-service和edas-web的部署目录。</p>
<h3 id="第二步：加入taobao-hsf-sar包"><a href="#第二步：加入taobao-hsf-sar包" class="headerlink" title="第二步：加入taobao-hsf.sar包"></a>第二步：加入taobao-hsf.sar包</h3><p>由于HSF应用的运行需要依赖于taobao-hsf.sar包，且与应用在同一个目录下，为此需要将taobao-hsf.sar分别放在/opt/software/edas-service和/opt/software/edas-web目录下。</p>
<h3 id="第三步：部署应用war包"><a href="#第三步：部署应用war包" class="headerlink" title="第三步：部署应用war包"></a>第三步：部署应用war包</h3><p>将war包分别存放到edas-web和edas-service目录下</p>
<h3 id="第四步：启动应用容器"><a href="#第四步：启动应用容器" class="headerlink" title="第四步：启动应用容器"></a>第四步：启动应用容器</h3><p>这是应用容器化运行的最后一步，我们需要在启动命令中设置的项目有：</p>
<ol>
<li><p>容器以守护模式运行：-d</p>
</li>
<li><p>在容器的hosts文件中加入HSF的配置中心地址：–add-host=[域名]:[IP]</p>
</li>
<li><p>指定容器映射端口：-p [宿主IP]:[容器内的应用IP]</p>
</li>
<li><p>设定一个有意义的容器名称：–name [容器名称]</p>
</li>
<li><p>挂载部署目录：-v [宿主目录]:[容器内目录]</p>
</li>
<li><p>指定启动脚本，如此处的/root/run.sh<br>启动命令如下所示：</p>
</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker run --add-host=jmenv.tbsite.net:10.148.16.72 -d -p 8080:8080 -v /opt/software/edas-web/:/usr/local/alitomcat/deploy/ --name edas-web yangyi/alitomcat:0.2 /root/run.sh</div></pre></td></tr></table></figure>
<h3 id="部署完毕！"><a href="#部署完毕！" class="headerlink" title="部署完毕！"></a>部署完毕！</h3><p>查看运行中的容器：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker ps</div></pre></td></tr></table></figure>
<h2 id="5-3-容器应用监控"><a href="#5-3-容器应用监控" class="headerlink" title="5.3. 容器应用监控"></a>5.3. 容器应用监控</h2><h3 id="5-3-1-单机容器应用监控"><a href="#5-3-1-单机容器应用监控" class="headerlink" title="5.3.1. 单机容器应用监控"></a>5.3.1. 单机容器应用监控</h3><p>​ cAdvisor为Docker容器用户提供了了解运行时容器资源使用和性能特征的工具。cAdvisor的容器抽象基于Google的lmctfy容器栈，因此原生支持Docker容器并能够“开箱即用”地支持其他的容器类型。cAdvisor部署为一个运行中的daemon，它会收集、聚集、处理并导出运行中容器的信息。这些信息能够包含容器级别的资源隔离参数、资源的历史使用状况、反映资源使用和网络统计数据完整历史状况的柱状图。</p>
<p>cAdvisor图形化监控工具是免费的，并且还开源。另外，它的资源消耗也比较低。但是，它有它的局限性，它只能监控一个Docker主机。</p>
<h4 id="获取cAdvisor镜像"><a href="#获取cAdvisor镜像" class="headerlink" title="获取cAdvisor镜像"></a>获取cAdvisor镜像</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker pull google/cadvisor</div></pre></td></tr></table></figure>
<h4 id="启动运行cAdvisor"><a href="#启动运行cAdvisor" class="headerlink" title="启动运行cAdvisor"></a>启动运行cAdvisor</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker run -v /:/rootfs:ro -v /var/run:/var/run:rw -v /sys:/sys:ro -v /var/lib/docker/:/var/lib/docker:ro -p 8888:8080 --detach=true --name cadvisor google/cadvisor:latest</div></pre></td></tr></table></figure>
<p>接下来就可以通过<a href="http://10.153.216.13:8888/" target="_blank" rel="external"><u>http://10.153.216.19:8888/</u></a> 访问图形化监控界面。</p>
<h3 id="5-3-2-集群容器应用监控"><a href="#5-3-2-集群容器应用监控" class="headerlink" title="5.3.2. 集群容器应用监控"></a>5.3.2. 集群容器应用监控</h3><p>在集群环境下，可以使用cAdvisor+ InfluxDB+grafana方案对应用进行监控。</p>
<p>其中InfluxDB是一个开源分布式时序、事件和指标数据库。使用Go语言编写，无需外部依赖。其设计目标是实现分布式和水平伸缩扩展。</p>
<p>Grafana是一款开源的监控绘图工具。可以实时收集、存储、显示时间序列类型的数据。</p>
<h4 id="第一步：下载镜像"><a href="#第一步：下载镜像" class="headerlink" title="第一步：下载镜像"></a>第一步：下载镜像</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker pull tutum/influxdb</div><div class="line"></div><div class="line">[root@xxxx]# docker pull grafana/grafana</div><div class="line"></div><div class="line">[root@xxxx]# docker pull google/cadvisor</div></pre></td></tr></table></figure>
<p>（在前面单机监控已经下载）</p>
<h4 id="第二步：启动InfluxDB"><a href="#第二步：启动InfluxDB" class="headerlink" title="第二步：启动InfluxDB"></a>第二步：启动InfluxDB</h4><p>启动InfluxDB容器，以守护模式运行，配置端口映射，并且暴露8090和8099端口，将该容器命名为influxsrv，启动命令如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker run -d -p 8083:8083 -p 8086:8086 --expose 8090 --expose 8099 --name influxsrv tutum/influxdb</div></pre></td></tr></table></figure>
<h4 id="第三步：安装cAdvisor专用的数据库"><a href="#第三步：安装cAdvisor专用的数据库" class="headerlink" title="第三步：安装cAdvisor专用的数据库"></a>第三步：安装cAdvisor专用的数据库</h4><p>启动InfluxDB之后，需要进入容器中配置为cAdvisor服务的数据库：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker exec -it influxsrv /bin/bash</div></pre></td></tr></table></figure>
<p>在容器内运行：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# influx</div></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">[]# CREATE DATABASE cadvisor</div><div class="line"></div><div class="line">[]# use cadvisor</div><div class="line"></div><div class="line">[]# CREATE USER “root” WITH PASSWORD ‘root’ WITH ALL PRIVILEGES</div></pre></td></tr></table></figure>
<p>退出</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# exit</div></pre></td></tr></table></figure>
<p>####第四步：启动cAdvisor容器</p>
<p>启动cAdvisor容器，这个cAdvisor容器需要绑定到前面的InfluxDB容器上：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker run -v/:/rootfs:ro -v /var/run:/var/run:rw -v /sys:/sys:ro -v /var/lib/docker/:/var/lib/docker:ro -p 8888:8080 --detach=true --link influxsrv:influxsrv --name cadvisor google/cadvisor:latest -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086</div></pre></td></tr></table></figure>
<p>在集群中的其他机器上只需要部署一个cAdvisor容器即可，启动时设定-storage_driver_host为安装了InfluxDB的宿主机IP和端口号，另外这里不再需要使用–link参数，启动命令如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker run -v/:/rootfs:ro -v /var/run:/var/run:rw -v /sys:/sys:ro -v /var/lib/docker/:/var/lib/docker:ro -p 8888:8080 --detach=true --name cadvisor google/cadvisor:latest -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=10.153.216.19:8086</div></pre></td></tr></table></figure>
<p>另外，可以通过各个主机的cAdvisor端口访问单机的监控图形化界面，如：<a href="http://10.153.216.13:8888/" target="_blank" rel="external"><u>http://10.153.216.19:8888/</u></a> <a href="http://10.153.216.13:8888/" target="_blank" rel="external"><u>http://10.153.216.13:8888/</u></a></p>
<h4 id="第五步：启动Grafana容器"><a href="#第五步：启动Grafana容器" class="headerlink" title="第五步：启动Grafana容器"></a>第五步：启动Grafana容器</h4><p>启动grafana容器：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[root@xxxx]# docker run -d -p 3000:3000 -e INFLUXDB_HOST=10.153.216.19 -e INFLUXDB_PORT=8086 -e INFLUXDB_NAME=cadvisor -e INFLUXDB_USER=root -e INFLUXDB_PASS=root --link influxsrv:influxsrv --name grafana grafana/grafana</div></pre></td></tr></table></figure>
<h4 id="第六步：访问集群监控界面"><a href="#第六步：访问集群监控界面" class="headerlink" title="第六步：访问集群监控界面"></a>第六步：访问集群监控界面</h4><p>最后通过<a href="http://10.153.216.19:3000访问集群监控界面，缺省用户及密码都是admin。" target="_blank" rel="external">http://10.153.216.19:3000访问集群监控界面，缺省用户及密码都是admin。</a></p>
<p>通过简单的图表项目配置即可查看监控信息。</p>
<p># </p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2017/03/13/docker-e5-be-ae-e6-9c-8d-e5-8a-a1-e5-ae-b9-e5-99-a8-e5-8c-96-e5-bc-80-e5-8f-91-e6-8e-a2-e7-b4-a2/" data-id="cj359k3tu0000fwv7ty81wr3i" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-drds-e7-bb-93-e5-90-88mybatis-e8-a7-a3-e5-86-b3-e6-96-b9-e6-a1-88-e4-b8-8e-e8-a7-84-e8-8c-83" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2017/01/20/drds-e7-bb-93-e5-90-88mybatis-e8-a7-a3-e5-86-b3-e6-96-b9-e6-a1-88-e4-b8-8e-e8-a7-84-e8-8c-83/" class="article-date">
  <time datetime="2017-01-20T12:52:25.000Z" itemprop="datePublished">2017-01-20</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/未分类/">未分类</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2017/01/20/drds-e7-bb-93-e5-90-88mybatis-e8-a7-a3-e5-86-b3-e6-96-b9-e6-a1-88-e4-b8-8e-e8-a7-84-e8-8c-83/">DRDS结合Mybatis解决方案与规范</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h3 id="为了方便，在正文之前先附上初稿的PDF文件下载链接DRDS结合Mybatis解决方案与规范-可以在线阅读。"><a href="#为了方便，在正文之前先附上初稿的PDF文件下载链接DRDS结合Mybatis解决方案与规范-可以在线阅读。" class="headerlink" title="为了方便，在正文之前先附上初稿的PDF文件下载链接DRDS结合Mybatis解决方案与规范 可以在线阅读。"></a>为了方便，在正文之前先附上初稿的PDF文件下载链接<a href="http://blog.hankchan.cn/wp-content/uploads/2017/03/DRDS结合Mybatis解决方案与规范.pdf" target="_blank" rel="external">DRDS结合Mybatis解决方案与规范</a> 可以在线阅读。</h3><h1 id="1-DRDS的Mybatis解决方案与规范"><a href="#1-DRDS的Mybatis解决方案与规范" class="headerlink" title="1.DRDS的Mybatis解决方案与规范"></a><strong><strong>1.DRDS的Mybatis解决方案与规范</strong></strong></h1><p>在运用DRDS分布式数据库时，考虑到使用DRUID数据库连接池技术，在配合使用Mybatis持久化框架的时候，采用Mybatis整合Spring，配置Mapper包扫描器的方式进行开发。</p>
<p>Mybatis使用范例见：swif-dao-drds项目的ScoreDemo相关的实体类、Mapper接口类和MapperXML文件。</p>
<h2 id="1-1-Mybatis整合Spring"><a href="#1-1-Mybatis整合Spring" class="headerlink" title="1.1. **Mybatis整合Spring**"></a><strong>1.1. <em>**</em></strong>Mybatis整合Spring<em>**</em></h2><p>配Mybatis整合Spring的主要工作包括：添加项目依赖，在Spring配置文件中配置管理Mybatis需要的Bean。完成这两部分工作即可。</p>
<h3 id="1-1-1-添加项目依赖"><a href="#1-1-1-添加项目依赖" class="headerlink" title="1.1.1. **添加项目依赖**"></a><strong>1.1.1. <em>**</em></strong>添加项目依赖<em>**</em></h3><p>在已有的Spring基础环境的基础上，为了整合Mybatis，首先需要加入相关的依赖包，包括：mybatis核心包，spring-mybatis整合包。</p>
<h3 id="1-1-2-配置Mybatis的Bean"><a href="#1-1-2-配置Mybatis的Bean" class="headerlink" title="1.1.2. **配置Mybatis的Bean**"></a><strong>1.1.2. <em>**</em></strong>配置Mybatis的Bean<em>**</em></h3><p>Mybatis框架的核心是SqlSessionFactory，我们将SqlSessionFactory交给Spring管理，通过依赖注入方式来使用它。为了统一管理，我们创建一个Spring的配置文件：application-mybatis.xml用于管理Mybatis相关的Bean。</p>
<h4 id="1-1-2-1-SqlSessionFactory"><a href="#1-1-2-1-SqlSessionFactory" class="headerlink" title="1.1.2.1. **SqlSessionFactory**"></a><strong>1.1.2.1. <em>**</em></strong>SqlSessionFactory<em>**</em></h4><p>我们在配置SqlSessionFactory的时候，需要配置两个属性，一个是数据源DataSource，由于使用了DRUID数据库连接池，所以dataSource就是DRUID（前面关于DRDS配置中已经进行了说明）；另一个是configLocation，用于加载Mybatis的全局配置文件的属性。实际配置信息如下图：</p>
<h4 id="1-1-2-2-MapperScannerConfigurer"><a href="#1-1-2-2-MapperScannerConfigurer" class="headerlink" title="1.1.2.2. **MapperScannerConfigurer**"></a><strong>1.1.2.2. <em>**</em></strong>MapperScannerConfigurer<em>**</em></h4><p>MapperScannerConfigurer是Mybatis用于自动扫描MapperXML文件的扫描器类，它可以自动将指定包路径下MapperXML中的MapperStatement和Mapper接口纳入Mybatis的管理中。</p>
<p>需要配置的属性是：包路径basePackage，用于指定被扫描的包含MapperXML和Mapper接口类的包路径，当需要多个包时，使用英文逗号分隔。另一个是SqlSessionFactoryBeanName，用于注入SqlSessionFactory。具体配置情况如下图：</p>
<h4 id="1-1-2-3-DataSourceTransactionManager"><a href="#1-1-2-3-DataSourceTransactionManager" class="headerlink" title="1.1.2.3. **DataSourceTransactionManager**"></a><strong>1.1.2.3. <em>**</em></strong>DataSourceTransactionManager<em>**</em></h4><p>dataSourceTransactionManager是Mybatis的事务管理器，只需要配置dataSource数据源一个属性即可，如下图所示：</p>
<h2 id="1-2-Mybatis全局配置文件"><a href="#1-2-Mybatis全局配置文件" class="headerlink" title="1.2. **Mybatis全局配置文件**"></a><strong>1.2. <em>**</em></strong>Mybatis全局配置文件<em>**</em></h2><p>在使用Mybatis过程中，相关的全局配置信息可以交给Mybatis的全局配置文件进行管理。我们将该全局配置文件命名为：mybatis.xml，并且放在classpath路径下。</p>
<h3 id="1-2-1-全局配置文件的结构"><a href="#1-2-1-全局配置文件的结构" class="headerlink" title="1.2.1. **全局配置文件的结构**"></a><strong>1.2.1. <em>**</em></strong>全局配置文件的结构<em>**</em></h3><p>MyBatis 的 XML 配置文件包含了影响 MyBatis 的设置和属性信息。XML 文档的高层级结构如下图：</p>
<p>其中的环境配置和映射器配置不需要进一步配置，这是因为在Mybatis整合Spring的时候已经通过Spring配置管理。</p>
<p>configuration配置可以用于配置一些全局的配置信息，常用的有Settings设置、TypeAliases类型别名等。</p>
<h4 id="1-2-1-1-Settings"><a href="#1-2-1-1-Settings" class="headerlink" title="1.2.1.1. **Settings**"></a><strong>1.2.1.1. <em>**</em></strong>Settings<em>**</em></h4><p>Settings可以用于管理Mybatis的缓存、超时时间等信息。对此暂时不需要做进一步配置，可以忽略。</p>
<h4 id="1-2-1-2-TypeAliases"><a href="#1-2-1-2-TypeAliases" class="headerlink" title="1.2.1.2. **TypeAliases**"></a><strong>1.2.1.2. <em>**</em></strong>TypeAliases<em>**</em></h4><p>类型别名是为Java类型命名一个短的名字。它只和MapperXML配置有关，只用来减少类完全限定名的多余部分，比如：</p>
<p>如此一来在MapperXML配置文件中编写SQL Statement语句的resultType、parameterType设置相应的Java类的时候就可以不需要写全类名，简化开发。比如：</p>
<p>需要注意的是在配置的时候不要同resultMap属性混淆，resultMap是在MapperXML文件中定义的。</p>
<p>下面是一些Mybatis定义好的Java类及其对应的别名，可以直接使用。</p>
<table><br><tbody><br><tr><br><td width="181">别名</td><br><td width="238">映射的类型</td><br></tr><br><tr><br><td width="181">_byte</td><br><td width="238">byte</td><br></tr><br><tr><br><td width="181">_long</td><br><td width="238">long</td><br></tr><br><tr><br><td width="181">_short</td><br><td width="238">short</td><br></tr><br><tr><br><td width="181">_int</td><br><td width="238">int</td><br></tr><br><tr><br><td width="181">_integer</td><br><td width="238">int</td><br></tr><br><tr><br><td width="181">_double</td><br><td width="238">double</td><br></tr><br><tr><br><td width="181">_float</td><br><td width="238">float</td><br></tr><br><tr><br><td width="181">_boolean</td><br><td width="238">boolean</td><br></tr><br><tr><br><td width="181">string</td><br><td width="238">String</td><br></tr><br><tr><br><td width="181">byte</td><br><td width="238">Byte</td><br></tr><br><tr><br><td width="181">long</td><br><td width="238">Long</td><br></tr><br><tr><br><td width="181">short</td><br><td width="238">Short</td><br></tr><br><tr><br><td width="181">int</td><br><td width="238">Integer</td><br></tr><br><tr><br><td width="181">integer</td><br><td width="238">Integer</td><br></tr><br><tr><br><td width="181">double</td><br><td width="238">Double</td><br></tr><br><tr><br><td width="181">float</td><br><td width="238">Float</td><br></tr><br><tr><br><td width="181">boolean</td><br><td width="238">Boolean</td><br></tr><br><tr><br><td width="181">date</td><br><td width="238">Date</td><br></tr><br><tr><br><td width="181">decimal</td><br><td width="238">BigDecimal</td><br></tr><br><tr><br><td width="181">bigdecimal</td><br><td width="238">BigDecimal</td><br></tr><br><tr><br><td width="181">object</td><br><td width="238">Object</td><br></tr><br><tr><br><td width="181">map</td><br><td width="238">Map</td><br></tr><br><tr><br><td width="181">hashmap</td><br><td width="238">HashMap</td><br></tr><br><tr><br><td width="181">list</td><br><td width="238">List</td><br></tr><br><tr><br><td width="181">arraylist</td><br><td width="238">ArrayList</td><br></tr><br><tr><br><td width="181">collection</td><br><td width="238">Collection</td><br></tr><br><tr><br><td width="181">iterator</td><br><td width="238">Iterator</td><br></tr><br></tbody><br></table>

<h4 id="1-2-1-3-Mybatis全局配置文件示例"><a href="#1-2-1-3-Mybatis全局配置文件示例" class="headerlink" title="1.2.1.3. **Mybatis全局配置文件示例**"></a><strong>1.2.1.3. <em>**</em></strong>Mybatis全局配置文件示例<em>**</em></h4><p>Mybatis全局配置文件本质上是一个XML文件，需要在文件头添加相应的信息。示例如下：</p>
<h2 id="1-3-接入DRUID数据源"><a href="#1-3-接入DRUID数据源" class="headerlink" title="1.3. **接入DRUID数据源**"></a><strong>1.3. <em>**</em></strong>接入DRUID数据源<em>**</em></h2><p>在前面接入DRDS中已经提到了关于配置DURID连接池的说明，也就是通过Spring管理DRUID的Bean。</p>
<h2 id="1-4-规定一些开发规范"><a href="#1-4-规定一些开发规范" class="headerlink" title="1.4. **规定一些开发规范**"></a><strong>1.4. <em>**</em></strong>规定一些开发规范<em>**</em></h2><p>这部分开发规范包括了Mybatis定义的、根据实际开发情况自定义的规范。遵循这样一套规范更有利于开发维护工作。</p>
<p>更多的规范还要进一步补充，可以根据实际应用的需要作进一步的调整。</p>
<h3 id="1-4-1-关于包名"><a href="#1-4-1-关于包名" class="headerlink" title="1.4.1. **关于包名**"></a><strong>1.4.1. <em>**</em></strong>关于包名<em>**</em></h3><h4 id="1-4-1-1-实体类包名"><a href="#1-4-1-1-实体类包名" class="headerlink" title="1.4.1.1. **实体类包名**"></a><strong>1.4.1.1. <em>**</em></strong>实体类包名<em>**</em></h4><p>实体类包主要用于存放实体类。</p>
<p>实体类的规范主要集中在统一类型的包命名上。我们定义形如：xxx.yyy.entity的包用于存放相关的实体类，并且仅仅用于存放实体类。</p>
<h4 id="1-4-1-2-Mapper包名"><a href="#1-4-1-2-Mapper包名" class="headerlink" title="1.4.1.2. **Mapper包名**"></a><strong>1.4.1.2. <em>**</em></strong>Mapper包名<em>**</em></h4><p>Mapper包名都需要统一纳入MapperScannerConfigurer的Bean的管理。</p>
<p>Mapper包统一规定用于存放Mapper接口类和Mapper接口类对应的MapperXML文件。</p>
<p>并且我们规定，每个Mapper包的命名方式向其对应的实体类包相近，就是说它们唯一同一个上级包下的不同包，如实体类包名为：xxx.yyy.entity，那么Mapper包名则是形如：xxx.yyy.mapper的格式。</p>
<h3 id="1-4-2-关于MapperXML和Mapper接口类命名"><a href="#1-4-2-关于MapperXML和Mapper接口类命名" class="headerlink" title="1.4.2. **关于MapperXML和Mapper接口类命名**"></a><strong>1.4.2. <em>**</em></strong>关于MapperXML和Mapper接口类命名<em>**</em></h3><h4 id="1-4-2-1-MapperXML文件名和Mapper类名"><a href="#1-4-2-1-MapperXML文件名和Mapper类名" class="headerlink" title="1.4.2.1. **MapperXML文件名和Mapper类名**"></a><strong>1.4.2.1. <em>**</em></strong>MapperXML文件名和Mapper类名<em>**</em></h4><p>按照Mybatis的约定，当实体类名为Xxxx.java的时候，其对应的Mapper接口类名为：XxxxMapper.java；其对应的MapperXML文件名为：XxxxMapper.xml。</p>
<h4 id="1-4-2-2-命名空间命名"><a href="#1-4-2-2-命名空间命名" class="headerlink" title="1.4.2.2. **命名空间命名**"></a><strong>1.4.2.2. <em>**</em></strong>命名空间命名<em>**</em></h4><p>在MapperXML中，命名空间namespace是唯一的，我们使用该MapperXML相对应的Mapper接口类全类名作为命名空间。</p>
<h3 id="1-4-3-关于Mapper接口类的方法的传入参数"><a href="#1-4-3-关于Mapper接口类的方法的传入参数" class="headerlink" title="1.4.3. **关于Mapper接口类的方法的传入参数**"></a><strong>1.4.3. <em>**</em></strong>关于Mapper接口类的方法的传入参数<em>**</em></h3><h4 id="1-4-3-1-自定义Criteria类的使用"><a href="#1-4-3-1-自定义Criteria类的使用" class="headerlink" title="1.4.3.1. **自定义Criteria类的使用**"></a><strong>1.4.3.1. <em>**</em></strong>自定义Criteria类的使用<em>**</em></h4><p>我们使用了自定义的一个传入参数信息类Criteria.java来接收SQL的传入参数信息。本质上，Criteria是一个映射集合Map，通过继承了HashMap类同时自定义实现了许多实用的方法用于设置传入参数。</p>
<p>在使用Mybatis过程中，传入参数除了简单类型和普通Java类以外，将Criteria规定为唯一的合法传入参数信息类。当需要使用Map、List或者更加复杂的传入参数时，都通过Criteria类来进行设置。</p>
<p>允许使用多个Criteria作为传入参数，我们通过Mybatis提供的注解@Param(“VALUE”)来修饰Criteria参数。使得MapperStatement语句可以对多个Criteria参数进行区别处理。</p>
<h5 id="1-4-3-1-1-接口方法示例"><a href="#1-4-3-1-1-接口方法示例" class="headerlink" title="1.4.3.1.1. **接口方法示例**"></a><strong>1.4.3.1.1. <em>**</em></strong>接口方法示例<em>**</em></h5><p>示例如下图：</p>
<h5 id="1-4-3-1-2-MapperXML示例"><a href="#1-4-3-1-2-MapperXML示例" class="headerlink" title="1.4.3.1.2. **MapperXML示例**"></a><strong>1.4.3.1.2. <em>**</em></strong>MapperXML示例<em>**</em></h5><p>如此一来，就可以在MapperXML中通过占位符#{aaa.bbb}或${aaa.bbb}方式进行参数匹配。示例如下图：</p>
<h4 id="1-4-3-2-封装对象类JavaBean的使用"><a href="#1-4-3-2-封装对象类JavaBean的使用" class="headerlink" title="1.4.3.2. **封装对象类JavaBean的使用**"></a><strong>1.4.3.2. <em>**</em></strong>封装对象类JavaBean的使用<em>**</em></h4><p>我们根据业务需求，除了使用自定义的Criteria类之外，定义了封装输入参数信息的JavaBean对象。直接将对象作为输入参数，由Mybatis的OGNL支持可以自动映射相应的参数占位符。</p>
<p>为了便于区别输入参数信息类和数据表实体类的区别，我们统一规定其命名方式为：“实体类名+”Example””作为输入参数信息类的类名。如下图Mapper接口类方法中的例子：</p>
<h5 id="1-4-3-2-1-接口示例方法"><a href="#1-4-3-2-1-接口示例方法" class="headerlink" title="1.4.3.2.1. **接口示例方法**"></a><strong>1.4.3.2.1. <em>**</em></strong>接口示例方法<em>**</em></h5><p>如下图所示，通过@Param注解修饰让该对象等同于使用Criteria自定义类，不同在于，Mybatis映射Criteria（本质上是一个HashMap）是通过映射该自定义的Key去寻找相应的Value，而对于JavaBean对象，Mybatis则是通过SetterGetter方法去查找相应的值。</p>
<h5 id="1-4-3-2-2-MapperXML示例"><a href="#1-4-3-2-2-MapperXML示例" class="headerlink" title="1.4.3.2.2. **MapperXML示例**"></a><strong>1.4.3.2.2. <em>**</em></strong>MapperXML示例<em>**</em></h5><p>由于Mybatis对OGNL的支持，配合@Param注解，对于JavaBean的输入参数，MapperXML中的定义可以更加简单，如下图：</p>
<p>可以看到，输入参数parameterType都可以直接省略不写。</p>
<h3 id="1-4-4-关于占位符、元素Id的命名"><a href="#1-4-4-关于占位符、元素Id的命名" class="headerlink" title="1.4.4. **关于占位符、元素Id的命名**"></a><strong>1.4.4. <em>**</em></strong>关于占位符、元素Id的命名<em>**</em></h3><p>由于Mybatis的属性占位符支持OGNL，所以可以通过英文句点“.”的方式映射Java Bean对象或者Map的key的对应值。如：#{criteria.location.province}，${criteria.location.province}等。</p>
<h4 id="1-4-4-1-在XML中设定值的占位符的命名"><a href="#1-4-4-1-在XML中设定值的占位符的命名" class="headerlink" title="1.4.4.1. **在XML中设定值的占位符的命名**"></a><strong>1.4.4.1. <em>**</em></strong>在XML中设定值的占位符的命名<em>**</em></h4><p>由于SQL片段中的占位符可以直接在引用在片段的时候定义值，于是我们统一规定，这一类在引用的时候才定义的占位符统一使用下划线开头的命名方式，形如：_scoreFunction。</p>
<p>以下为示例：</p>
<p>被引用的片段如下：</p>
<p>引用SQL的语句定义如下：</p>
<p>有一点需要注意的是，在定义占位符属性值的时候，存在一个值的作用域的问题。比如说在嵌套引用的关系中，都对同一个占位符${..}赋值。</p>
<p>这个时候，Mybatis将会按照就近原则，从最近的嵌套元素中匹配相应的值，如下图所示，同一个属性设置两次，起作用的将会是由红色下划线标识的部分。</p>
<p>由此，我们也规定：对于这类属性占位符的赋值，尽可能在嵌套引用的最外层处理，最理想的状态是在&lt;select&gt;等MapperStatement中在进行赋值，这样的SQL片段更加有利于提高重用性。具体应用场景需要根据该占位符的作用来灵活地选择方案。</p>
<h4 id="1-4-4-2-输入参数占位符命名"><a href="#1-4-4-2-输入参数占位符命名" class="headerlink" title="1.4.4.2. **输入参数占位符命名**"></a><strong>1.4.4.2. <em>**</em></strong>输入参数占位符命名<em>**</em></h4><p>我们统一规定，采用驼峰式方式为属性占位符命名，即：以字母开头，并且首个单词首字母小写其他单词首字母大写的方式，如：#{departmentId}或${departmentId}。</p>
<p>对于复杂的属性映射，在每个占位符中使用英文句点分隔，命名方式不因存在该句点而变化，如：#{criteria.departmentId}或${criteria.departmentId}。</p>
<h4 id="1-4-4-3-lt-sql-gt-元素Id的命名"><a href="#1-4-4-3-lt-sql-gt-元素Id的命名" class="headerlink" title="1.4.4.3. **&lt;sql&gt;元素Id的命名**"></a><strong>1.4.4.3. <em>**</em></strong>&lt;sql&gt;元素Id的命名<em>**</em></h4><p>为了增加辨识度，我们统一规定，所有的&lt;sql&gt;元素的Id命名均采用以下划线分隔、首字符大写的命名方式。如：</p>
<p>当该条SQL包含较为完整的SELECT/UPDATE、FROM、WHERE等要素以致足以构成一条可用SQL的时候，就用“_SQL”作为元素Id的结尾。如：</p>
<h4 id="1-4-4-4-MapperStatement的Id的命名"><a href="#1-4-4-4-MapperStatement的Id的命名" class="headerlink" title="1.4.4.4. **MapperStatement的Id的命名**"></a><strong>1.4.4.4. <em>**</em></strong>MapperStatement的Id的命名<em>**</em></h4><p>MapperStatement就是Mybatis生成的用于执行的SQL语句所在的元素节点。也就是&lt;select&gt;,&lt;update&gt;,&lt;delete&gt;,&lt;insert&gt;元素的内容。其Id值就是对应的MapperStatement的Id。</p>
<p>按照规定，每一条MapperStatement的Id都在对应的Mapper接口类中有相应的同名方法。</p>
<p>所以我们在命名MapperStatement的时候，同样规定采用驼峰式的命名方式。</p>
<p>值得注意的是，我们可以在Mapper接口类中重载这些方法，Mybatis只要形参中定义的输入参数最终能够匹配该MapperStatement中所需要的属性占位符即可。例子如下：</p>
<h3 id="1-4-5-关于SqlSessionFactory的使用"><a href="#1-4-5-关于SqlSessionFactory的使用" class="headerlink" title="1.4.5. **关于SqlSessionFactory的使用**"></a><strong>1.4.5. <em>**</em></strong>关于SqlSessionFactory的使用<em>**</em></h3><h4 id="1-4-5-1-注入SqlSessionFactory依赖"><a href="#1-4-5-1-注入SqlSessionFactory依赖" class="headerlink" title="1.4.5.1. **注入SqlSessionFactory依赖**"></a><strong>1.4.5.1. <em>**</em></strong>注入SqlSessionFactory依赖<em>**</em></h4><p>在开发DAO层的时候，主要是通过使用SqlSessionFactory来创建SqlSession，再由SqlSession去完成数据库相关的操作。对于SqlSessionFactory的使用，主要推荐两种使用方式。</p>
<p>其一：通过Setter注入方式将其作为数据库操作基类的一个属性，如下图所示：</p>
<p>其二：通过注解方式依赖注入，如下图所示：</p>
<p>两者相比较而言，Setter注入方式需要在依赖SqlSessionFactory的类中配置sqlSessionFactory的属性，而注解方式不需要。但是从单元测试角度而言，Setter注入方式更加方便。</p>
<h4 id="1-4-5-2-SqlSession的使用"><a href="#1-4-5-2-SqlSession的使用" class="headerlink" title="1.4.5.2. **SqlSession的使用**"></a><strong>1.4.5.2. <em>**</em></strong>SqlSession的使用<em>**</em></h4><p>我们已经知道，操作Mybatis执行SQL语句的关键是通过SqlSession。在这里，关于SqlSession的使用主要有以下几点需要特别注意：</p>
<ol>
<li><p>SqlSession的作用域必须控制在一个方法内而不是类的全局。如下图：</p>
</li>
<li><p>SqlSession是通过sqlSessionFactory.openSession(<strong><strong>boolean</strong></strong>autoCommit)创建的，当形参为空时默认是false，但是当需要操作的SQL涉及数据的变化时，就需要显性地将形参设置为true。</p>
</li>
<li>建议使用：<br><strong><strong>  <em>**</em></strong></strong>try<strong><strong>(SqlSession sqlSession = sqlSessionFactory.openSession()) { //</strong></strong>coding here <em>**</em>}</li>
</ol>
<p>的方式作为SqlSession的创建方式。</p>
<h2 id="1-5-MapperXML的基本使用"><a href="#1-5-MapperXML的基本使用" class="headerlink" title="1.5. **MapperXML的基本使用**"></a><strong>1.5. <em>**</em></strong>MapperXML的基本使用<em>**</em></h2><p>建议查看swift-dao-drds中的范例（ScoreDemoMapper.xml文件）的使用。</p>
<h3 id="1-5-1-select"><a href="#1-5-1-select" class="headerlink" title="1.5.1. **select**"></a><strong>1.5.1. <em>**</em></strong>select<em>**</em></h3><p>如下图示例，这是最基础的一条SELECT查询语句，该语句id为”get”，需要跟Mapper接口类的一个方法名相同。输入参数parameterType为”GuandSurfH”，这里再次强调parameterType、resultType的值是在全局配置文件中定义的别名。而返回结果集的”GuandSurfH”，则是在MapperXML文件中定义的。</p>
<p><strong><strong>参数占位符说明</strong></strong></p>
<p>#{ObserveTime}</p>
<p>这就告诉MyBatis创建一个PreparedStatement（预处理语句）参数。使用JDBC，这样的一个参数在SQL中会由一个“?”来标识，并被传递到一个新的预处理语句中。</p>
<p>${_timeSuffix}</p>
<p>这是一个拼接字符串占位符，其参数会直接作为一个字符串拼接到SQL语句中去。</p>
<p>就像下面这样：</p>
<p>// 相似的JDBC代码</p>
<p>String get=”SELECT * FROM t_guand_surf_h201610 WHERE ObserveTime=? AND StationID=?”;</p>
<p>PreparedStatement ps = conn.prepareStatement(get);</p>
<p>// ……</p>
<h3 id="1-5-2-insert、update、delete"><a href="#1-5-2-insert、update、delete" class="headerlink" title="1.5.2. **insert、update、delete**"></a><strong>1.5.2. <em>**</em></strong>insert、update、delete<em>**</em></h3><p>数据修改语句 insert，update 和 delete 在它们的实现中非常相似。</p>
<h3 id="1-5-3-sql"><a href="#1-5-3-sql" class="headerlink" title="1.5.3. **sql**"></a><strong>1.5.3. <em>**</em></strong>sql<em>**</em></h3><p>sql这个元素可以被用来定义可重用的SQL代码段，可以包含在其他语句中。如下图示例：</p>
<p>在定义好sql元素的片段之后，可以在MapperXML范围内引用该SQL片段，方法是使用include元素，如下图：</p>
<p>该元素的内容将作为一个完整的语句片段被包含在其他的语句中，并且支持sql元素的嵌套引用。可以结合foreach元素设计开发复杂的动态SQL语句，如多表UNION、JOIN查询等。</p>
<h3 id="1-5-4-resultMap"><a href="#1-5-4-resultMap" class="headerlink" title="1.5.4. **resultMap**"></a><strong>1.5.4. <em>**</em></strong>resultMap<em>**</em></h3><p>resultMap是定义在MapperXML文件中的用于映射SQL返回结果的自定义集合。可以根据需求自定义查询结果集中的列名与JavaBean实体类的映射关系。</p>
<h2 id="1-6-一些开发维护的建议"><a href="#1-6-一些开发维护的建议" class="headerlink" title="1.6. **一些开发维护的建议**"></a><strong>1.6. <em>**</em></strong>一些开发维护的建议<em>**</em></h2><p>为了应对多变的需求的开发维护，总结一些开发新功能、维护改进旧功能的小技巧。这是建立在基础环境搭建完成的前提下的。提供了一些简单的开发步骤说明，更复杂的场景还需要进一步思考选择，不必拘泥。</p>
<h3 id="1-6-1-开发新需求"><a href="#1-6-1-开发新需求" class="headerlink" title="1.6.1. **开发新需求**"></a><strong>1.6.1. <em>**</em></strong>开发新需求<em>**</em></h3><p>开发新需求主要指的是，需要重新开始一个子模块功能（诸如：station、typhoon、score都单独认为是一个子模块功能类）的开发。我们推荐按照以下大致步骤进行。</p>
<h4 id="1-6-1-1-创建实体类"><a href="#1-6-1-1-创建实体类" class="headerlink" title="1.6.1.1. **创建实体类**"></a><strong>1.6.1.1. <em>**</em></strong>创建实体类<em>**</em></h4><p>从数据库表作为出发点，首先创建命名新的包名用于存放数据表对应的实体类，然后根据数据表编写设计实体类。Setter和Getter方法是必不可少的。</p>
<h4 id="1-6-1-2-创建Mapper接口和MapperXML"><a href="#1-6-1-2-创建Mapper接口和MapperXML" class="headerlink" title="1.6.1.2. **创建Mapper接口和MapperXML**"></a><strong>1.6.1.2. <em>**</em></strong>创建Mapper接口和MapperXML<em>**</em></h4><p>首先根据前面提到的命名规范，创建Mapper的包用于存放Mapper接口和MapperXML文件。然后根据实体类的情况创建相应的XxxMapper接口类和XxxMapper.xml文件。</p>
<h4 id="1-6-1-3-加到Mybatis扫描器"><a href="#1-6-1-3-加到Mybatis扫描器" class="headerlink" title="1.6.1.3. **加到Mybatis扫描器**"></a><strong>1.6.1.3. <em>**</em></strong>加到Mybatis扫描器<em>**</em></h4><p>找到Mybatis相关的Spring的配置文件，找到Mybatis包扫描器MapperScannerConfigurer，在其属性basePackage值中加入新增的Mapper接口和MapperXML所在包的包名，多个包使用英文逗号分隔，如下图：</p>
<h4 id="1-6-1-4-编写MapperXML"><a href="#1-6-1-4-编写MapperXML" class="headerlink" title="1.6.1.4. **编写MapperXML**"></a><strong>1.6.1.4. <em>**</em></strong>编写MapperXML<em>**</em></h4><p>首先，每一个Mybatis的MapperXML文件都必须有以下的文档头：</p>
<p>接着，在根节点&lt;mapper&gt;中需要设置命名空间属性namespace，在命名方式规范中有提到。</p>
<p>然后，首先拿到需要运行的SQL语句，根据SQL语句返回结果集考虑如何选择定义ResultMap。</p>
<p>在ResultMap中，其type属性值建议仍然使用其对应的实体类的全类名，除非返回结果集在对应的实体类中已经无法适用，否则不建议重新创建定义新的实体类去接收返回结果对象。</p>
<p>如下图，需要返回的结果集不同，但是仍然可以基于相同的实体类，那些没有被映射的字段将会是默认值：</p>
<h3 id="1-6-2-维护原有业务"><a href="#1-6-2-维护原有业务" class="headerlink" title="1.6.2. **维护原有业务**"></a><strong>1.6.2. <em>**</em></strong>维护原有业务<em>**</em></h3><p>维护原有业务主要包括：在原有的基础上进行的功能修改、删减、增加等操作。而且是有SQL语句变化需求的操作。</p>
<p>具体说就是在SQL语句层面的维护，业务层的维护修改不涉及SQL语句故而不属于该范畴。这一部分需要功能需求入手，主要说明了需要注意的事项。</p>
<h4 id="1-6-2-1-功能修改、删减"><a href="#1-6-2-1-功能修改、删减" class="headerlink" title="1.6.2.1. **功能修改、删减**"></a><strong>1.6.2.1. <em>**</em></strong>功能修改、删减<em>**</em></h4><p>对于功能修改删减而言，我们可以从被调用的Mapper接口类的方法作为起点，在对应的MapperXML中找到与该方法名同名的MapperStatement，通过修改MapperStatement对功能进行修改。</p>
<p>如果MapperStatement中包含了引用语句，尽可能不要改动被引用的SQL片段，你不知道哪里还有引用，确实需要修改引用语句的，你也要确保所有引用该片段的语句功能变化是受你控制的。</p>
<p>建议是首先考虑新增一个SQL片段去适应新的功能需求，弃用原来引用的SQL片段。如果功能改动很大，请酌情选择涉及的相关引用了该SQL片段的语句。</p>
<h4 id="1-6-2-2-功能新增"><a href="#1-6-2-2-功能新增" class="headerlink" title="1.6.2.2. **功能新增**"></a><strong>1.6.2.2. <em>**</em></strong>功能新增<em>**</em></h4><p>功能新增主要场景有：当需要加入新的SQL语句或者需要在原有的SQL语句中加入某些查询列、查询条件、排序条件等内容。</p>
<p>对于加入新的SQL语句，可以直接创建一条新的SQL片段，查找原有的基础SQL片段中是否有可重用的，引入进来使用即可。</p>
<p>对于需要在原有SQL语句中加入一些列、条件的场景，主要有两种解决办法。</p>
<p>其一：在原有的SQL语句中，通过拼接字符串的占位符${…}，然后在引用该SQL的地方对这些占位符进行赋值（如加入列名、查询条件等）即可。</p>
<p>其二：重新创建新的SQL片段，更换原来的MapperStatement的引用。</p>
<p>可以根据应用场景灵活选择一种方案。</p>
<h2 id="1-7-可能遇到的问题"><a href="#1-7-可能遇到的问题" class="headerlink" title="1.7. **可能遇到的问题**"></a><strong>1.7. <em>**</em></strong>可能遇到的问题<em>**</em></h2><h3 id="1-7-1-集合类转换异常问题"><a href="#1-7-1-集合类转换异常问题" class="headerlink" title="1.7.1. **集合类转换异常问题**"></a><strong>1.7.1. <em>**</em></strong>集合类转换异常问题<em>**</em></h3><p><strong><strong>报错信息：</strong></strong></p>
<p>org.apache.ibatis.exceptions.PersistenceException:</p>
<h3 id="Error-querying-database-Cause-java-lang-IllegalArgumentException-invalid-comparison-java-util-Arrays-ArrayList-and-java-lang-String"><a href="#Error-querying-database-Cause-java-lang-IllegalArgumentException-invalid-comparison-java-util-Arrays-ArrayList-and-java-lang-String" class="headerlink" title="Error querying database.  Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Arrays$ArrayList and java.lang.String"></a>Error querying database.  Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Arrays$ArrayList and java.lang.String</h3><h3 id="Cause-java-lang-IllegalArgumentException-invalid-comparison-java-util-Arrays-ArrayList-and-java-lang-String"><a href="#Cause-java-lang-IllegalArgumentException-invalid-comparison-java-util-Arrays-ArrayList-and-java-lang-String" class="headerlink" title="Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Arrays$ArrayList and java.lang.String"></a>Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Arrays$ArrayList and java.lang.String</h3><p><strong><strong>说明：</strong></strong></p>
<p>在使用Mybatis编写MapperXML中，我们已经知道可以通过</p>
<p>的方式判断输入参数是否为null等。</p>
<p>但是如果被判断的是一个集合对象，当使用如上判断方式就会出错。因为集合无法判断是否不等于空字符串。当输入参数不是集合类，也就不存在该问题。</p>
<p><strong><strong>解决方法：</strong></strong></p>
<p>去掉判断语句中的集合不等于空字符串的判断即可。</p>
<h3 id="1-7-2-无法映射结果集问题"><a href="#1-7-2-无法映射结果集问题" class="headerlink" title="1.7.2. **无法映射结果集问题**"></a><strong>1.7.2. <em>**</em></strong>无法映射结果集问题<em>**</em></h3><p><strong><strong>报错信息：</strong></strong></p>
<p>org.apache.ibatis.exceptions.PersistenceException:</p>
<h3 id="Error-querying-database-Cause-org-apache-ibatis-executor-ExecutorException-A-query-was-run-and-no-Result-Maps-were-found-for-the-Mapped-Statement-‘swift-data-score-mapper-ScoreDemoMapper-getTsScoreProvinceStatisticor’-It’s-likely-that-neither-a-Result-Type-nor-a-Result-Map-was-specified"><a href="#Error-querying-database-Cause-org-apache-ibatis-executor-ExecutorException-A-query-was-run-and-no-Result-Maps-were-found-for-the-Mapped-Statement-‘swift-data-score-mapper-ScoreDemoMapper-getTsScoreProvinceStatisticor’-It’s-likely-that-neither-a-Result-Type-nor-a-Result-Map-was-specified" class="headerlink" title="Error querying database.  Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement ‘swift.data.score.mapper.ScoreDemoMapper.getTsScoreProvinceStatisticor’.  It’s likely that neither a Result Type nor a Result Map was specified."></a>Error querying database.  Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement ‘swift.data.score.mapper.ScoreDemoMapper.getTsScoreProvinceStatisticor’.  It’s likely that neither a Result Type nor a Result Map was specified.</h3><p><strong><strong>说明：</strong></strong></p>
<p>如果漏写了ResultType或者ResultMap（二者不可同时使用），将会出现该报错信息。</p>
<p><strong><strong>解决方法：</strong></strong></p>
<p>只需要在MapperStatement中补上ResultType或者ResultMap即可。</p>
<h3 id="1-7-3-多个参数映射失败问题"><a href="#1-7-3-多个参数映射失败问题" class="headerlink" title="1.7.3. **多个参数映射失败问题**"></a><strong>1.7.3. <em>**</em></strong>多个参数映射失败问题<em>**</em></h3><p><strong><strong>报错信息：</strong></strong></p>
<p>org.apache.ibatis.exceptions.PersistenceException:</p>
<h3 id="Error-querying-database-Cause-org-apache-ibatis-binding-BindingException-Parameter-‘criteria’-not-found-Available-parameters-are-1-jobType-param1-param2"><a href="#Error-querying-database-Cause-org-apache-ibatis-binding-BindingException-Parameter-‘criteria’-not-found-Available-parameters-are-1-jobType-param1-param2" class="headerlink" title="Error querying database.  Cause: org.apache.ibatis.binding.BindingException: Parameter ‘criteria’ not found. Available parameters are [1, jobType, param1, param2]"></a>Error querying database.  Cause: org.apache.ibatis.binding.BindingException: Parameter ‘criteria’ not found. Available parameters are [1, jobType, param1, param2]</h3><h3 id="Cause-org-apache-ibatis-binding-BindingException-Parameter-‘criteria’-not-found-Available-parameters-are-1-jobType-param1-param2"><a href="#Cause-org-apache-ibatis-binding-BindingException-Parameter-‘criteria’-not-found-Available-parameters-are-1-jobType-param1-param2" class="headerlink" title="Cause: org.apache.ibatis.binding.BindingException: Parameter ‘criteria’ not found. Available parameters are [1, jobType, param1, param2]"></a>Cause: org.apache.ibatis.binding.BindingException: Parameter ‘criteria’ not found. Available parameters are [1, jobType, param1, param2]</h3><p><strong><strong>说明：</strong></strong></p>
<p>当Mapper接口类中的方法是多个参数的时候，需要添加@Param注解修饰参数，因为Mybatis会默认以“param1, param2, …”的形式去解析输入参数，参数名并没有实际意义。</p>
<p>更简单的情况是，当只有一个输入参数时，类似的报错是值没有定义该Parameter。</p>
<p><strong><strong>解决方法：</strong></strong></p>
<p>为每个输入参数都用@Param注解定义其输入参数映射值Parameter即可避免该问题。或者检查提示的Parameter是否已经定义。</p>
<h3 id="1-7-4-符号格式错误问题"><a href="#1-7-4-符号格式错误问题" class="headerlink" title="1.7.4. **符号格式错误问题**"></a><strong>1.7.4. <em>**</em></strong>符号格式错误问题<em>**</em></h3><p><strong><strong>报错信息：</strong></strong></p>
<p>org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘scoreDemoMapper’ defined in file [D:\qxt\swift-edas-2\swift-dao-drds\target\classes\swift\data\score\mapper\ScoreDemoMapper.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: org.apache.ibatis.builder.BuilderException: Error creating document instance.  Cause: org.xml.sax.SAXParseException; lineNumber: 31; columnNumber: 22; 元素内容必须由格式正确的字符数据或标记组成。</p>
<p><strong><strong>说明：</strong></strong></p>
<p>这个报错信息指出XML格式错误，可能是某些节点没有闭合等，但是很大程度是因为使用了诸如“&lt;”、“&gt;”等影响XML解析的字符。</p>
<p><strong><strong>解决方法：</strong></strong></p>
<p>对这类型的字符需要使用格式转换符替换，如小于号“&lt;”使用“&lt;”代替，大于号“&gt;”使用“&gt;”代替。常见的几种替换符如下：</p>
<p><table></table></p>
<p><tbody></tbody></p>
<p><tr></tr></p>
<p><td width="104">&lt;</td></p>
<p><td width="86">&lt;</td></p>
<p><td width="91">小于</td><br></p>
<p><tr></tr></p>
<p><td width="104">&gt;</td></p>
<p><td width="86">&gt;</td></p>
<p><td width="91">大于</td><br></p>
<p><tr></tr></p>
<p><td width="104">&amp;</td></p>
<p><td width="86">&amp;</td></p>
<p><td width="91">和号</td><br></p>
<p><tr></tr></p>
<p><td width="104">‘</td></p>
<p><td width="86">‘</td></p>
<p><td width="91">省略号</td><br></p>
<p><tr></tr></p>
<p><td width="104">“</td></p>
<p><td width="86">“</td></p>
<p><td width="91">引号</td><br><br><br><br>还可以使用CDATA的形式替换特殊的字符，即使用：“&lt;![CDATA[”开始，由“]]&gt;” 结束的形式避免其中的内容被XML解析。如下图：</p>
<h1 id="2-附：MybatisGenerator的运用与否"><a href="#2-附：MybatisGenerator的运用与否" class="headerlink" title="2. **附：MybatisGenerator的运用与否**"></a><strong>2. <em>**</em></strong>附：MybatisGenerator的运用与否<em>**</em></h1><p>MybatisGenerator（以下简称：MBG）的使用主要有两步：配置配置文件、运行生成结果。</p>
<h2 id="2-1-运行生成"><a href="#2-1-运行生成" class="headerlink" title="2.1. **运行生成**"></a><strong>2.1. <em>**</em></strong>运行生成<em>**</em></h2><p>需要首先配置好相应的配置文件信息，才可以执行运行命令。由于配置文件信息内容较多，所以放在下一节进行说明。</p>
<p>通过Maven执行MybatisGenerator自动生成结果的命令如下：</p>
<p>mvn mybatis-generator:generate</p>
<h2 id="2-2-MybatisGenerator的配置"><a href="#2-2-MybatisGenerator的配置" class="headerlink" title="2.2. **MybatisGenerator的配置**"></a><strong>2.2. <em>**</em></strong>MybatisGenerator的配置<em>**</em></h2><p>使用默认的generatorConfig.xml文件名作为其配置信息文件。</p>
<p>详细配置信息参考Demo的generatorConfig.xml文件。下面只介绍几个重点且常用的配置说明。详细的配置信息说明可以参考官方文档：</p>
<p><a href="http://mbg.cndocs.tk/configreference/xmlconfig.html" target="_blank" rel="external"><u>http://mbg.cndocs.tk/configreference/xmlconfig.html</u></a></p>
<p>或者参考：</p>
<p><a href="http://www.jianshu.com/p/e09d2370b796" target="_blank" rel="external"><u>http://www.jianshu.com/p/e09d2370b796</u></a></p>
<p>用到的最多的配置主要有：数据库连接的jar包；数据库连接信息；生成的实体类信息；生成的Mapper类信息；生成的MapperXML信息；Java类解析器信息；自定义的目标数据库表的信息。</p>
<h3 id="2-2-1-数据库连接"><a href="#2-2-1-数据库连接" class="headerlink" title="2.2.1. **数据库连接**"></a><strong>2.2.1. <em>**</em></strong>数据库连接<em>**</em></h3><p>以使用MySQL数据库为例子，首先需要引入数据库连接的jar包，如下图所示：</p>
<p>然后在&lt;jdbcConnection&gt;元素节点下配置数据库连接的信息，包括驱动类、连接地址、用户名和密码等，如下图：</p>
<h3 id="2-2-2-目标实体类配置"><a href="#2-2-2-目标实体类配置" class="headerlink" title="2.2.2. **目标实体类配置**"></a><strong>2.2.2. <em>**</em></strong>目标实体类配置<em>**</em></h3><p>使用javaModelGenerator元素配置目标实体类的生成包信息，如下图所示：</p>
<p>配置数据库连接</p>
<p>配置生成的实体类</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2017/01/20/drds-e7-bb-93-e5-90-88mybatis-e8-a7-a3-e5-86-b3-e6-96-b9-e6-a1-88-e4-b8-8e-e8-a7-84-e8-8c-83/" data-id="cj359k3u70001fwv7brz8obrp" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-springmvc-thymeleaf-e5-bc-80-e5-8f-91-e6-8c-87-e5-8d-97-demo" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2017/01/03/springmvc-thymeleaf-e5-bc-80-e5-8f-91-e6-8c-87-e5-8d-97-demo/" class="article-date">
  <time datetime="2017-01-03T06:12:44.000Z" itemprop="datePublished">2017-01-03</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/SpringMVC/">SpringMVC</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2017/01/03/springmvc-thymeleaf-e5-bc-80-e5-8f-91-e6-8c-87-e5-8d-97-demo/">SpringMVC-Thymeleaf-开发指南-Demo</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p><a href="http://120.24.213.246/wp-content/uploads/2017/03/SpringMVC-Thymeleaf-开发指南.pdf" target="_blank" rel="external">SpringMVC-Thymeleaf-开发指南</a></p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2017/01/03/springmvc-thymeleaf-e5-bc-80-e5-8f-91-e6-8c-87-e5-8d-97-demo/" data-id="cj359k40g001lfwv7obkylr72" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-nginx-e4-bb-a3-e7-90-86-e6-9c-8d-e5-8a-a1-e5-99-a8-e6-96-b9-e5-bc-8f-e8-a7-a3-e5-86-b3-e5-ae-9e-e9-99-85-e5-bc-80-e5-8f-91-e4-b8-adajax-e8-af-b7-e6-b1-82-e7-9a-84-e8-b7-a8-e5-9f-9f-e9-97-ae-e9-a2-98" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2016/10/16/nginx-e4-bb-a3-e7-90-86-e6-9c-8d-e5-8a-a1-e5-99-a8-e6-96-b9-e5-bc-8f-e8-a7-a3-e5-86-b3-e5-ae-9e-e9-99-85-e5-bc-80-e5-8f-91-e4-b8-adajax-e8-af-b7-e6-b1-82-e7-9a-84-e8-b7-a8-e5-9f-9f-e9-97-ae-e9-a2-98/" class="article-date">
  <time datetime="2016-10-16T11:02:29.000Z" itemprop="datePublished">2016-10-16</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Nginx/">Nginx</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2016/10/16/nginx-e4-bb-a3-e7-90-86-e6-9c-8d-e5-8a-a1-e5-99-a8-e6-96-b9-e5-bc-8f-e8-a7-a3-e5-86-b3-e5-ae-9e-e9-99-85-e5-bc-80-e5-8f-91-e4-b8-adajax-e8-af-b7-e6-b1-82-e7-9a-84-e8-b7-a8-e5-9f-9f-e9-97-ae-e9-a2-98/">Nginx代理服务器方式解决实际开发中Ajax请求的跨域问题</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="这是最简单的解决方案示例，具体开发中可以根据不同的需求在此基础上去实现更加贴合开发需求的解决方案。"><a href="#这是最简单的解决方案示例，具体开发中可以根据不同的需求在此基础上去实现更加贴合开发需求的解决方案。" class="headerlink" title="这是最简单的解决方案示例，具体开发中可以根据不同的需求在此基础上去实现更加贴合开发需求的解决方案。"></a>这是最简单的解决方案示例，具体开发中可以根据不同的需求在此基础上去实现更加贴合开发需求的解决方案。</h2><h3 id="提要：在实际开发中，由于前后端分离的需要，将后台web应用部署到服务器（Tomcat为例），而前端页面不与web应用耦合，另外部署到其他节点（本例中采用Nginx部署）这样就让后台专注于对外提供API接口，而在前端页面可以通过Ajax调用API接口获取相关的服务。前后端分离的好处就不过多赘述，主要简单介绍在这个过程中的ajax请求的跨域问题。"><a href="#提要：在实际开发中，由于前后端分离的需要，将后台web应用部署到服务器（Tomcat为例），而前端页面不与web应用耦合，另外部署到其他节点（本例中采用Nginx部署）这样就让后台专注于对外提供API接口，而在前端页面可以通过Ajax调用API接口获取相关的服务。前后端分离的好处就不过多赘述，主要简单介绍在这个过程中的ajax请求的跨域问题。" class="headerlink" title="提要：在实际开发中，由于前后端分离的需要，将后台web应用部署到服务器（Tomcat为例），而前端页面不与web应用耦合，另外部署到其他节点（本例中采用Nginx部署）这样就让后台专注于对外提供API接口，而在前端页面可以通过Ajax调用API接口获取相关的服务。前后端分离的好处就不过多赘述，主要简单介绍在这个过程中的ajax请求的跨域问题。"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#提要在实际开发中由于前后端分离的需要将后台web应用部署到服务器tomcat为例而前端页面不与web应用耦合另外部署到其他节点本例中采用nginx部署这样就让后台专注于对外提供api接口而在前端页面可以通过ajax调用api接口获取相关的服务前后端分离的好处就不过多赘述主要简单介绍在这个过程中的ajax请求的跨域问题" target="_blank" rel="external"></a>提要：在实际开发中，由于前后端分离的需要，将后台web应用部署到服务器（Tomcat为例），而前端页面不与web应用耦合，另外部署到其他节点（本例中采用Nginx部署）这样就让后台专注于对外提供API接口，而在前端页面可以通过Ajax调用API接口获取相关的服务。前后端分离的好处就不过多赘述，主要简单介绍在这个过程中的ajax请求的跨域问题。</h3><h3 id="原理：就是利用了Nginx作为代理服务器，将所有的请求啊应用部署地址啊等等等等都proxy到Nginx这里，也就是说这样一来就相当于前后端虽然部署的位置不同，但是经过代理服务器这么一折腾，就不属于跨域调用啦！"><a href="#原理：就是利用了Nginx作为代理服务器，将所有的请求啊应用部署地址啊等等等等都proxy到Nginx这里，也就是说这样一来就相当于前后端虽然部署的位置不同，但是经过代理服务器这么一折腾，就不属于跨域调用啦！" class="headerlink" title="原理：就是利用了Nginx作为代理服务器，将所有的请求啊应用部署地址啊等等等等都proxy到Nginx这里，也就是说这样一来就相当于前后端虽然部署的位置不同，但是经过代理服务器这么一折腾，就不属于跨域调用啦！"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#原理就是利用了nginx作为代理服务器将所有的请求啊应用部署地址啊等等等等都proxy到nginx这里也就是说这样一来就相当于前后端虽然部署的位置不同但是经过代理服务器这么一折腾就不属于跨域调用啦" target="_blank" rel="external"></a>原理：就是利用了Nginx作为代理服务器，将所有的请求啊应用部署地址啊等等等等都proxy到Nginx这里，也就是说这样一来就相当于前后端虽然部署的位置不同，但是经过代理服务器这么一折腾，就不属于跨域调用啦！</h3><h3 id="下面开始吧！"><a href="#下面开始吧！" class="headerlink" title="下面开始吧！"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#下面开始吧" target="_blank" rel="external"></a>下面开始吧！</h3><h3 id="1-关于web项目（项目命名为：chats-web）开发（主要关注注解-RequestMapping的value值）"><a href="#1-关于web项目（项目命名为：chats-web）开发（主要关注注解-RequestMapping的value值）" class="headerlink" title="1. 关于web项目（项目命名为：chats-web）开发（主要关注注解@RequestMapping的value值）"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#1-关于web项目项目命名为chats-web开发主要关注注解requestmapping的value值" target="_blank" rel="external"></a>1. 关于web项目（项目命名为：<em>chats-web</em>）开发（主要关注注解<code>@RequestMapping</code>的value值）</h3><p>在开发web项目过程中，我采用了Spring+SpringMVC+Mybatis框架。通过SpringMVC的控制器设计api接口。 具体APIs接口示例代码如下：</p>
<pre><code>    @RequestMapping(&quot;/test.json&quot;)
    public @ResponseBody APIResult test(HttpServletRequest req, HttpServletResponse rsp) {
        APIResult result = APIResult.prepare();
        Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        map.put(&quot;name&quot;, &quot;DavidBeckham&quot;);
        map.put(&quot;age&quot;, &quot;35&quot;);
        map.put(&quot;team&quot;, &quot;Man United&quot;);
        map.put(&quot;time&quot;, new Date().toString());
        return map != null ? result.ok(map) : result.error(&quot;failure..&quot;);
    }
`&lt;/pre&gt;

### [](https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#2-关于前端项目项目命名为portal开发主要关注ajax请求的url参数)2\. 关于前端项目（项目命名为：_portal_）开发（主要关注Ajax请求的URL参数）

在portal项目开发中，主要是使用HTML+JS/jQuery开发。其中一个页面的调用设计如下：
&lt;pre&gt;`function ajaxReq() {           
        var url=&quot;/chats-web/test.json&quot;;
        var oData = document.getElementById(&apos;data&apos;);
        $.getJSON(url, function (json) {                                
            oData.innerHTML = &quot;name:&quot; + json.result.name + &quot;; team:&quot; + json.result.team;
        });
}
`&lt;/pre&gt;

#### [](https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#通过上述的前后端项目开发如果将两个项目分离部署显然存在一个ajax请求的跨域问题有一种解决方法是jsonp但是这个方法的缺点是只能使用get的请求方式)通过上述的前后端项目开发，如果将两个项目分离部署，显然存在一个ajax请求的跨域问题。有一种解决方法是JSONP，但是这个方法的缺点是只能使用GET的请求方式。

#### [](https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#于是所以我们采用另外的一种解决方案代理服务器解决方案在此使用nginx作为代理服务器)于是所以我们采用另外的一种解决方案：代理服务器解决方案（在此使用Nginx作为代理服务器）。

### [](https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#3-关于nginx配置部署chats-web应用和portal应用并且解决ajax跨域问题的方法)3\. 关于Nginx配置部署chats-web应用和portal应用并且解决Ajax跨域问题的方法

#### [](https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#31-chats-web应用部署)3.1 chats-web应用部署

我们将chats-web应用部署在tomcat服务器的8080端口，使用war部署就行啦。

#### [](https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#32-portal应用部署)3.2 portal应用部署

portal应用就是静态页面而已，通过调用api接口去渲染页面。所以将portal项目放在一个本地目录中。本例中放在：`/home/hankchan101/html/chats-portal`中。 注意index页面实际目录为：`/home/hankchan101/html/chats-portal/portal/index.html`。

#### [](https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#33-配置nginx)3.3 配置Nginx

完成两个项目的部署之后，就可以开始配置Nginx的配置文件啦。 nginx.conf配置文件关键内容如下：
&lt;pre&gt;`#配置反向代理的地址
upstream chats-web {
    server 192.168.154.162:8080;
}       

server {
listen   80;
server_name  192.168.154.162;                       
    root /home/hankchan101/html/chats-portal;   

    #将所有请求都转到portal项目所在路径
    location / {  
        root /home/hankchan101/html/chats-portal;  
    }

    #将/chats-web开头的请求（API接口调用的请求）配置反向代理
    location /chats-web {       
        proxy_pass http://chats-web;          
    }
    #略
    #略
}
</code></pre><h4 id="Bingo！最简单的示例解决方案完成！"><a href="#Bingo！最简单的示例解决方案完成！" class="headerlink" title="Bingo！最简单的示例解决方案完成！"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/Nginx%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E5%86%B3Ajax%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%E9%97%AE%E9%A2%98.md#bingo最简单的示例解决方案完成" target="_blank" rel="external"></a>Bingo！最简单的示例解决方案完成！</h4><p>虽然现在看起来很简单，其实也是费了不少时间Google了好久爬了好半天坑才解决的。总算是对自己有个交代哈哈哈</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2016/10/16/nginx-e4-bb-a3-e7-90-86-e6-9c-8d-e5-8a-a1-e5-99-a8-e6-96-b9-e5-bc-8f-e8-a7-a3-e5-86-b3-e5-ae-9e-e9-99-85-e5-bc-80-e5-8f-91-e4-b8-adajax-e8-af-b7-e6-b1-82-e7-9a-84-e8-b7-a8-e5-9f-9f-e9-97-ae-e9-a2-98/" data-id="cj359k3zk001cfwv70tnl3tj2" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-e5-b7-a5-e5-8e-82-e6-a8-a1-e5-bc-8f-e5-92-8c-e5-8d-95-e4-be-8b-e6-a8-a1-e5-bc-8f-e7-bb-93-e5-90-88-e5-ba-94-e7-94-a8-e7-9a-84-e5-9c-ba-e6-99-af-e6-80-9d-e8-80-83" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2016/10/11/e5-b7-a5-e5-8e-82-e6-a8-a1-e5-bc-8f-e5-92-8c-e5-8d-95-e4-be-8b-e6-a8-a1-e5-bc-8f-e7-bb-93-e5-90-88-e5-ba-94-e7-94-a8-e7-9a-84-e5-9c-ba-e6-99-af-e6-80-9d-e8-80-83/" class="article-date">
  <time datetime="2016-10-11T15:03:05.000Z" itemprop="datePublished">2016-10-11</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/未分类/">未分类</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2016/10/11/e5-b7-a5-e5-8e-82-e6-a8-a1-e5-bc-8f-e5-92-8c-e5-8d-95-e4-be-8b-e6-a8-a1-e5-bc-8f-e7-bb-93-e5-90-88-e5-ba-94-e7-94-a8-e7-9a-84-e5-9c-ba-e6-99-af-e6-80-9d-e8-80-83/">工厂模式和单例模式结合应用的场景思考</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h3 id="工厂模式可以应用于根据条件创建实例对象的场景中，单例模式可以应用于配置文件的读取中。"><a href="#工厂模式可以应用于根据条件创建实例对象的场景中，单例模式可以应用于配置文件的读取中。" class="headerlink" title="工厂模式可以应用于根据条件创建实例对象的场景中，单例模式可以应用于配置文件的读取中。"></a>工厂模式可以应用于根据条件创建实例对象的场景中，单例模式可以应用于配置文件的读取中。</h3><h4 id="比如，在以下场景中，我们需要实现原始数据的读取操作："><a href="#比如，在以下场景中，我们需要实现原始数据的读取操作：" class="headerlink" title="比如，在以下场景中，我们需要实现原始数据的读取操作："></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#比如在以下场景中我们需要实现原始数据的读取操作" target="_blank" rel="external"></a>比如，在以下场景中，我们需要实现原始数据的读取操作：</h4><h4 id="一共有三种原始数据来源：1-数据库；2-NC文件；3-缓存。"><a href="#一共有三种原始数据来源：1-数据库；2-NC文件；3-缓存。" class="headerlink" title="一共有三种原始数据来源：1. 数据库；2. NC文件；3. 缓存。"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#一共有三种原始数据来源1-数据库2-nc文件3-缓存" target="_blank" rel="external"></a>一共有三种原始数据来源：1. 数据库；2. NC文件；3. 缓存。</h4><h4 id="我们定义了一个原始数据读取接口：GetDataInterface-。该接口针对3种不同的数据来源有3个不同的实现类："><a href="#我们定义了一个原始数据读取接口：GetDataInterface-。该接口针对3种不同的数据来源有3个不同的实现类：" class="headerlink" title="我们定义了一个原始数据读取接口：GetDataInterface()。该接口针对3种不同的数据来源有3个不同的实现类："></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#我们定义了一个原始数据读取接口getdatainterface该接口针对3种不同的数据来源有3个不同的实现类" target="_blank" rel="external"></a>我们定义了一个原始数据读取接口：GetDataInterface()。该接口针对3种不同的数据来源有3个不同的实现类：</h4><h4 id="1-DbGetDataInterfaceImpl-2-NcGetDataInterfaceImpl-3-CacheGetDataInterfaceImpl-。"><a href="#1-DbGetDataInterfaceImpl-2-NcGetDataInterfaceImpl-3-CacheGetDataInterfaceImpl-。" class="headerlink" title="1. DbGetDataInterfaceImpl(); 2. NcGetDataInterfaceImpl();3. CacheGetDataInterfaceImpl()。"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#1-dbgetdatainterfaceimpl-2-ncgetdatainterfaceimpl3-cachegetdatainterfaceimpl" target="_blank" rel="external"></a>1. DbGetDataInterfaceImpl(); 2. NcGetDataInterfaceImpl();3. CacheGetDataInterfaceImpl()。</h4><h4 id="这个时候我们在实现原始数据调用的时候，会因为不同的来源去配置不同的实现，如果同一个数据调用的数据来源适用于以上几种方式，"><a href="#这个时候我们在实现原始数据调用的时候，会因为不同的来源去配置不同的实现，如果同一个数据调用的数据来源适用于以上几种方式，" class="headerlink" title="这个时候我们在实现原始数据调用的时候，会因为不同的来源去配置不同的实现，如果同一个数据调用的数据来源适用于以上几种方式，"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#这个时候我们在实现原始数据调用的时候会因为不同的来源去配置不同的实现如果同一个数据调用的数据来源适用于以上几种方式" target="_blank" rel="external"></a>这个时候我们在实现原始数据调用的时候，会因为不同的来源去配置不同的实现，如果同一个数据调用的数据来源适用于以上几种方式，</h4><h4 id="那么我们在更换原始数据来源的时候，就需要频繁的在源代码中重新创建不同的GetData实现类，不仅不利于维护，而且十分繁琐。"><a href="#那么我们在更换原始数据来源的时候，就需要频繁的在源代码中重新创建不同的GetData实现类，不仅不利于维护，而且十分繁琐。" class="headerlink" title="那么我们在更换原始数据来源的时候，就需要频繁的在源代码中重新创建不同的GetData实现类，不仅不利于维护，而且十分繁琐。"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#那么我们在更换原始数据来源的时候就需要频繁的在源代码中重新创建不同的getdata实现类不仅不利于维护而且十分繁琐" target="_blank" rel="external"></a>那么我们在更换原始数据来源的时候，就需要频繁的在源代码中重新创建不同的GetData实现类，不仅不利于维护，而且十分繁琐。</h4><h4 id="这个时候就可以考虑通过工厂模式，自动地根据需求去创建需要的实例对象！"><a href="#这个时候就可以考虑通过工厂模式，自动地根据需求去创建需要的实例对象！" class="headerlink" title="这个时候就可以考虑通过工厂模式，自动地根据需求去创建需要的实例对象！"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#这个时候就可以考虑通过工厂模式自动地根据需求去创建需要的实例对象" target="_blank" rel="external"></a>这个时候就可以考虑通过工厂模式，自动地根据需求去创建需要的实例对象！</h4><h4 id="光是这样恐怕还不够满足实际开发的需求！"><a href="#光是这样恐怕还不够满足实际开发的需求！" class="headerlink" title="光是这样恐怕还不够满足实际开发的需求！"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#光是这样恐怕还不够满足实际开发的需求" target="_blank" rel="external"></a>光是这样恐怕还不够满足实际开发的需求！</h4><h4 id="那么我们可以考虑，通过配置文件，设置数据调用的需求所对应的数据来源，然后由工厂模式根据配置文件自动创建实例对象！！"><a href="#那么我们可以考虑，通过配置文件，设置数据调用的需求所对应的数据来源，然后由工厂模式根据配置文件自动创建实例对象！！" class="headerlink" title="那么我们可以考虑，通过配置文件，设置数据调用的需求所对应的数据来源，然后由工厂模式根据配置文件自动创建实例对象！！"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#那么我们可以考虑通过配置文件设置数据调用的需求所对应的数据来源然后由工厂模式根据配置文件自动创建实例对象" target="_blank" rel="external"></a>那么我们可以考虑，通过配置文件，设置数据调用的需求所对应的数据来源，然后由工厂模式根据配置文件自动创建实例对象！！</h4><h4 id="基于此，那么我们就需要用到单例模式了！！"><a href="#基于此，那么我们就需要用到单例模式了！！" class="headerlink" title="基于此，那么我们就需要用到单例模式了！！"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#基于此那么我们就需要用到单例模式了" target="_blank" rel="external"></a>基于此，那么我们就需要用到单例模式了！！</h4><h4 id="通过开发读取配置文件的单例类，就可以在工厂模式中利用工厂类调用单例类以取得配置信息，根据配置信息去自动创建所需要的实例对象！Bingo！"><a href="#通过开发读取配置文件的单例类，就可以在工厂模式中利用工厂类调用单例类以取得配置信息，根据配置信息去自动创建所需要的实例对象！Bingo！" class="headerlink" title="通过开发读取配置文件的单例类，就可以在工厂模式中利用工厂类调用单例类以取得配置信息，根据配置信息去自动创建所需要的实例对象！Bingo！"></a><a href="https://github.com/hank0chan/blog/blob/master/201610/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%BB%93%E5%90%88%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF1.md#通过开发读取配置文件的单例类就可以在工厂模式中利用工厂类调用单例类以取得配置信息根据配置信息去自动创建所需要的实例对象bingo" target="_blank" rel="external"></a>通过开发读取配置文件的单例类，就可以在工厂模式中利用工厂类调用单例类以取得配置信息，根据配置信息去自动创建所需要的实例对象！Bingo！</h4>
      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2016/10/11/e5-b7-a5-e5-8e-82-e6-a8-a1-e5-bc-8f-e5-92-8c-e5-8d-95-e4-be-8b-e6-a8-a1-e5-bc-8f-e7-bb-93-e5-90-88-e5-ba-94-e7-94-a8-e7-9a-84-e5-9c-ba-e6-99-af-e6-80-9d-e8-80-83/" data-id="cj359k3x5000ifwv795hb9qtn" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-linux-e9-83-a8-e7-bd-b2-e5-9f-ba-e4-ba-8e-nginx-tomcat-e7-9a-84-e7-9a-84-web-e5-ba-94-e7-94-a8" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2016/10/01/linux-e9-83-a8-e7-bd-b2-e5-9f-ba-e4-ba-8e-nginx-tomcat-e7-9a-84-e7-9a-84-web-e5-ba-94-e7-94-a8/" class="article-date">
  <time datetime="2016-10-01T04:18:19.000Z" itemprop="datePublished">2016-10-01</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Linux/">Linux</a>►<a class="article-category-link" href="/categories/Linux/Nginx/">Nginx</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2016/10/01/linux-e9-83-a8-e7-bd-b2-e5-9f-ba-e4-ba-8e-nginx-tomcat-e7-9a-84-e7-9a-84-web-e5-ba-94-e7-94-a8/">Linux  部署基于 Nginx-Tomcat 的 的 WEB  应用</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>内容说明：<br>如何在 Linux 服务器 CentOS-6.7 版本上部署基于 Nginx + Tomcat 的 的 Web 应用？<br>注：CentOS-7 的改动较大，如防火墙、MySQL 的安装等。<br>应用场景：<br>简单Demo<br>概要：<br>首先确保成功安装 JavaJDK、Tomcat、Nginx。<br>其次配置 Tomcat 及 Nginx，让 Nginx 作为响应服务器接收请求。<br>配置 Tomcat 的配置信息，包括环境变量，端口号等内容。<br>配置 Nginx 的配置信息，包括反向代理，负载均衡等内容。<br>备注：<br>在此，JavaJDK 采用了默认的安装路径（安装初期没有做处理，建议自定义统一<br>存放）。自定义安装的软件统一存放在/usr/local 目录下。安装包存放在自定义<br>目录：/opt/setuppackages 。<br>如：<br>JAVAHOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el6_8.x86_64<br>CATALINA_HOME=/usr/local/tomcat<br>CATALINA2_HOME=/usr/local/tomcat2<br>以此类推。。<br>Nginx 目录：/usr/local/webservers/nginx<br>1. 安装 JavaJDK<br>1.1 安装步骤<br>1. 查询自带 java 版本：java -version<br>2. 测试网络连接：ping baidu.com<br>3. 若存在，卸载自带版本重装新版本（Google Now！略）。<br>4. 不存在，查看 java jdk 可安装的版本信息：yum -y list java<em><br>5. 选择安装其中一个版本（java8）：yum -y install java-1.8.0-openjdk</em><br>6. 等待安装完成（Complete！），查看 java 安装版本信息：java -version<br>7. 查看 jdk 安装路径：whereis java<br>8. centos linux 使用 yum 命令后，会将 OpenSDK 安装到/usr/lib/jvm/ 目录<br>9. 进入 jvm 目录：cd /usr/lib/jvm<br>10. 可以看到其中的一个目录文件（共两个）：drwxr-xr-x. 9 root root 4096 Sep 21<br>10:06 java-1.8.0-openjdk-1.8.0.101-3.b13.el6_8.x86_64（字符 d 开头）<br>11. 修改配置 Linux 的环境变量：vim /etc/profile<br>12. 进入该文件后再文件的最后添加以下内容：<br>export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el6_8.x86_64<br>export<br>CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/to<br>ols.jar<br>export PATH=$PATH:$JAVA_HOME/bin<br>13. 修改完成后保存关闭，执行配置立即生效：source /etc/profile<br>14. 分别输入命令：<br>echo $JAVA_HOME<br>echo $CLASSPATH<br>echo $PATH 查看配置是否生效。<br>1.2 测试运行 HelloWorld.java<br>1. 接下来写一个简单的 HelloWorld.java，并且执行 javac HelloWorld.java 及 java<br>Helloworld 命令查看是否能运行输出。<br>2. 运行成功说明配置完成！DONE！<br>2. 安装 Tomcat<br>2.1 安装步骤<br>1. 使用源码安装方式，下载安装包：<br>wget <a href="http://tomcat.apache.org/download-80.cgi" target="_blank" rel="external">http://tomcat.apache.org/download-80.cgi</a> ( 该地址无效)<br>2. 发现 wget command not found 错误<br>3. 使用命令 安装 wget：yum -y install wget<br>4. 安装 wget 命令成功，下载 Tomcat 安装包。。。<br>5. 使用其他 tomcat 下载地址（可自行 Google！）：<br>wget <a href="http://s.cnaaa11.com/soft/apache-tomcat-8.0.14.tar.gz" target="_blank" rel="external">http://s.cnaaa11.com/soft/apache-tomcat-8.0.14.tar.gz</a><br>6. 下载安装包成功！<br>7. 将安装包移动到自定义目录：<br>8. mv apache-tomcat-8.0.14.tar.gz /opt/setuppackages<br>9. 复制该安装包到安装目录下：<br>10. cp apache-tomcat-8.0.14.tar.gz /usr/local<br>11. 进入安装目录：cd /usr/local<br>12. 解压 Tomcat 安装包：tar -zxvf apache-tomcat-8.0.14.tar.gz<br>13. 解压完成后删除该目录中的安装包：rm -rf apache-tomcat-8.0.14.tar.gz<br>14. 执行命令（相当于更改名字）：mv apache-tomcat-8.0.14 tomcat<br>15. 现在可以看到/usr/local 目录下有一个 tomcat 的文件夹<br>16. 接下来启动 tomcat：/usr/local/tomcat/bin/startup.sh<br>17. 启动成功后防火墙开放 8080 端口（CentOS-7 的防火墙已经改版）<br>18. 开放 8080 端口方法<br>/sbin/iptables -I INPUT -p tcp –dport 8080 -j ACCEPT #开启 8080 （I 为大写 i）<br>/etc/rc.d/init.d/iptables save #保存配置<br>/etc/rc.d/init.d/iptables restart #重启防火墙<br>19. 如果是 centOS-7 版本，防火墙设置如下：<br>使用 firewalld 开放 Linux 端口：<br>开启端口<br>firewall-cmd –zone=public –add-port=80/tcp –permanent<br>命令含义：<br>–zone #作用域<br>–add-port=80/tcp #添加端口，格式为：端口/通讯协议<br>–permanent #永久生效，没有此参数重启后失效<br>重启防火墙<br>firewall-cmd –reload<br>20. 保存成功！<br>2.2 测试运行<br>1. 开始检查 tomcat 是否安装运行：在 hp 笔记本浏览器地址栏输入”ip:port”，如<br>输入：<a href="http://192.168.***.***:8080/（写上自己的虚拟机" target="_blank" rel="external">http://192.168.***.***:8080/（写上自己的虚拟机</a> IP 呀）<br>2. 查看是否能成功安装运行 Tomcat<br>2.3 配置环境变量<br>配置环境变量：CATALINA_HOME<br>同样的运行命令：vim /etc/profile 修改配置文件，加入以下内容：<br>export CATALINA_HOME=/usr/local/tomcat<br>保存退出，运行执行生效命令：source /etc/profile<br>运行成功！DONE！<br>2.4 重要配置（配合 Nginx ）<br>1. 安装第一个 Tomcat 不需要修改包括其目录下的 startup.sh、shutdown.sh、<br>catalina.sh 中的配置信息。 但是除此之外的 Tomcat 都需要对上述文件中做相应的<br>配置。<br>2. 除了第一个 Tomcat 以外的 Tomcat 应用，都需要在 startup.sh 和 shutdown.sh<br>中新增如下配置信息，示例如下：<br>export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el6_8.x86_64<br>export PATH=$PATH:$JAVA_HOME/bin<br>export CLASSPATH=$JAVA_HOME/lib<br>export CATALINA_HOME=$CATALINA2_HOME # 引用/etc/profile 中的配置<br>export CATALINA_BASE=$CATALINA2_BASE # 引用/etc/profile 中的配置<br>3. 除了第一个 Tomcat 以外的 Tomcat 应用， 在 catalina.sh 中新增加的配置信息如<br>下：<br>export CATALINA_HOME=/usr/local/tomcat2<br>export CATALINA_BASE=/usr/local/tomcat2<br>4. 每个 Tomcat 都需要在 server.xml 中设置端口号信息。<br>server.xml 中的配置信息示例：<br>指定端口号（略）；<br>5. 每个 Tomcat 设定的端口号都需要在防火墙设置中开放（注意 Centos 版本问<br>题），步骤如下：</p>
<p>#关闭防火墙<br>/etc/init.d/iptables stop<br>service iptables stop # 停止服务</p>
<p>#查看防火墙信息<br>/etc/init.d/iptables status</p>
<p>#开放端口:8080<br>/sbin/iptables -I INPUT -p tcp –dport 8080 -j ACCEPT</p>
<p>#将更改进行保存<br>/etc/rc.d/init.d/iptables save</p>
<p>#重启防火墙以便改动生效:(或者直接重启系统)<br>/etc/init.d/iptables restart<br>6. 每个 Tomcat 都需要在/etc/profile 中配置系统的环境变量:<br>/etc/profile 中的配置信息示例：</p>
<p>#1 首个 tomcat<br>export CATALINA_HOME=/usr/local/tomcat</p>
<p>#2 第二个 tomcat<br>CATALINA2_BASE=/usr/local/tomcat2<br>CATALINA2_HOME=/usr/local/tomcat2<br>export CATALINA2_BASE CATALINA2_HOME<br>TOMCAT2_HOME=/usr/local/tomcat2<br>export TOMCAT2_HOME<br>以此类推…<br>Tomcat 配置文件 server.xml 详细局部：<br>Context(表示一<br>个 web 应用程<br>序， 通常为 WAR<br>文件， 关于 WAR<br>的具体信息见<br>servlet 规范)<br>docBase 应用程序的路径或者是 WAR 文件存放的路径<br>path<br>表示此 web 应用程序的 url 的前缀，这样请求的 url 为<br><a href="http://localhost:8080/path/****" target="_blank" rel="external">http://localhost:8080/path/****</a><br>reloadable<br>这个属性非常重要，如果为 true，则 tomcat 会自动检测<br>应用程序的/WEB-INF/lib 和/WEB-INF/classes 目录的变<br>化，自动装载新的应用程序，我们可以在不重起 tomcat<br>的情况下改变应用程序<br>Host(表示一个<br>虚拟主机)<br>name 指定主机名<br>appBase 应用程序基本目录，即存放应用程序的目录<br>unpackWARs<br>如果为 true，则 tomcat 会自动将 WAR 文件解压，否则<br>不解压，直接从 WAR 文件中运行应用程序<br>3. 安装 Nginx<br>参考：<a href="http://www.runoob.com/linux/nginx-install-setup.html" target="_blank" rel="external">http://www.runoob.com/linux/nginx-install-setup.html</a><br>3.1 说明<br>Nginx 服务程序默认以 nobody 身份运行，建议为其创建专门的用户账号，以便<br>更准确地控制其访问权限，增加灵活性、降低安全风险。 如： 创建一个名为 nginx<br>的用户，不建立宿主目录，也禁止登录到 shell 环境。<br>3.2 安装步骤<br>1. 下载 PCRE 安装包：<br>wget <a href="http://downloads.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz" target="_blank" rel="external">http://downloads.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz</a><br>2. 解压安装包：tar zxvf pcre-8.35.tar.gz<br>3. 进入安装包目录：cd pcre-8.35<br>4. 执行编译安装：<br>./configure<br>make &amp;&amp; make install<br>注意：要先安装好 gcc 、gzip 和 和 openssl 依赖库<br>yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel open openssl-devel<br>5. 下载 Nginx 安装包：wget <a href="http://nginx.org/download/nginx-1.6.2.tar.gz" target="_blank" rel="external">http://nginx.org/download/nginx-1.6.2.tar.gz</a><br>6. 解压安装包：tar zxvf nginx-1.6.2.tar.gz<br>7. 执行编译（注意指定 pcre 安装目录）：<br>./configure –prefix=/usr/local/webserver/nginx –with-http_stub_status_module<br>–with-http_ssl_module –with-pcre=/usr/local/pcre-8.35<br>8. 执行两条命令安装：<br>make<br>make install<br>9. 查看版本信息：/usr/local/webserver/nginx/sbin/nginx -v<br>10. 到此，Nginx 安装完成！<br>3.3 配置 nginx.conf 配置文件<br>首先要修改的命令主要有：<br>sendfile on;<br>tcp_nopush on;</p>
<p>#keepalive_timeout 0;<br>keepalive_timeout 65;<br>然后在上述代码下方增加下面的配置信息：<br>upstream localhost {<br>server localhost:8080; # 第一个 Tomcat 的开放 ip+port<br>server localhost:9080; # 第二个 Tomcat 的开放 ip+port</p>
<h1 id="以此类推"><a href="#以此类推" class="headerlink" title="以此类推"></a>以此类推</h1><p>}<br>在 location / {<br>root html;<br>index index.html index.htm;位置下方添加下面的代码：<br>proxy_pass <a href="http://localhost" target="_blank" rel="external">http://localhost</a>;<br>proxy_redirect off;<br>proxy_set_header Host $host;<br>proxy_set_header X-Real-IP $remote_addr;<br>proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br>client_max_body_size 10m;<br>client_body_buffer_size 128k;<br>proxy_connect_timeout 15;<br>proxy_send_timeout 15;<br>proxy_read_timeout 15;<br>proxy_buffer_size 8k;<br>proxy_buffers 4 64k;<br>proxy_busy_buffers_size 128k;<br>proxy_temp_file_write_size 128k;<br>12. 常用的几个命令：<br>/usr/local/webserver/nginx/sbin/nginx -t #检查 nginx.conf 配置文件正确性<br>/usr/local/webserver/nginx/sbin/nginx -s reload # 重新载入配置文件<br>/usr/local/webserver/nginx/sbin/nginx -s reopen # 重启 Nginx<br>/usr/local/webserver/nginx/sbin/nginx -s stop # 停止 Nginx<br>13. DONE！<br>3.4 测试 Tomcat 应用<br>在浏览器中输入：&lt;ip&gt;:&lt;port&gt;。查看 Tomcat 应用是否成功安装启动。<br>3.5 测试负载均衡<br>安装配置完成后，分别进入每个 tomcat 安装路径的 ROOT 目录下（如：<br>/usr/local/tomcat/webapps/ROOT）。<br>在该目录下创建一个名称相同但是内容都不同的 JSP 文件，如 test.jsp。<br>在每个 tomcat 中的 test.jsp 文件中设置不同的内容，如：<br>&lt;html&gt;<br>&lt;body&gt;<br>This is the First（Second …以此类推）Tomcat Webapp Page..<br>&lt;/body&gt;<br>&lt;/html&gt;<br>最后启动不止一个 Tomcat 应用，在浏览器中输入：&lt;ip&gt;/test.jsp。每次点击发送<br>请求，得到的页面分别是 First、Second。。。等不同的内容说明负载均衡测试成<br>功！<br>其他后续更新。。。<br>4. 安装 MySQL<br>4.1 安装步骤<br>安装数据库，选用 MySQL 数据库。<br>通过 yum 的方式来进行 mysql 的数据库安装。<br>1. 首先我们可以输入： yum list | grep mysql<br>来查看 yum 上提供的 mysql 数据库可下载的版本。<br>2. 然后我们可以通过输入： yum install -y mysql-server mysql mysql-devel<br>将 mysql mysql-server mysql-devel 都安装好。<br>注意:安装mysql 时我们并不是安装了 mysql 客户端就相当于安装好了 mysql 数据<br>库了，我们还需要安装 mysql-server 服务端才行。<br>3. 等待了一番时间后，Compete！<br>4. 现在查看安装的 mysql 版本信息：rpm -qi mysql-server<br>5. 安装完毕。<br>对于 于 CentOS-7 而言， CentOS 7的yum源中不再有正常安装 mysql时的mysql-sever<br>文件，需要去官网上下载：</p>
<h1 id="wget-http-dev-mysql-com-get-mysql-community-release-el7-5-noarch-rpm"><a href="#wget-http-dev-mysql-com-get-mysql-community-release-el7-5-noarch-rpm" class="headerlink" title="wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm"></a>wget <a href="http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm" target="_blank" rel="external">http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm</a></h1><h1 id="rpm-ivh-mysql-community-release-el7-5-noarch-rpm"><a href="#rpm-ivh-mysql-community-release-el7-5-noarch-rpm" class="headerlink" title="rpm -ivh mysql-community-release-el7-5.noarch.rpm"></a>rpm -ivh mysql-community-release-el7-5.noarch.rpm</h1><h1 id="yum-install-mysql-community-server"><a href="#yum-install-mysql-community-server" class="headerlink" title="yum install mysql-community-server"></a>yum install mysql-community-server</h1><p>成功安装之后重启 mysql 服务</p>
<h1 id="service-mysqld-restart"><a href="#service-mysqld-restart" class="headerlink" title="service mysqld restart"></a>service mysqld restart</h1><p>4.2 启动登陆数据库<br>启动 MySQL：service mysqld start<br>1. 第一次启动会显示较多提示信息，下次启动则不会再次显示。<br>重启：service mysqld restart<br>2. 使用 mysql 数据库时，都得首先启动 mysqld 服务。<br>查看 MySQL 是否开机自动启动：chkconfig –list | grep mysqld<br>设置开机自动启动命令：chkconfig mysqld on<br>3. 登录 mysql 数据库：mysql -u root -p<br>4. 输入 SQL 测试：select 9*9 from dual;<br>5. 测试通过！<br>4.3 设置数据库密码<br>mysql 数据库安装完以后只会有一个 root 管理员账号， 但是此时的 root 账号还并<br>没有为其设置密码，设置数据库密码：<br>/usr/bin/mysqladmin -u root password ‘root’ # 密码建议设置为 root<br>4.4 常用配置文件<br>MySQL 主要配置文件： /etc/my.cnf<br>数据库文件： /var/lib/mysql<br>数据库日志文件位置： /var/log<br>5. 应用部署<br>说明：<br>在 Eclipse 中创建 Maven 测试项目 webapp_deploy_demo， export 为 war 包并且放<br>到 Linux 服务器中。<br>测试使用的应用demo源码及其war包存在目录： /home/hankchan101/demos 中。<br>然后将该应用部署在 Tomcat 服务容器中。主要有两种部署方式：war 包部署和<br>源代码部署方式。<br>5.1 war 包部署<br>说明：<br>参考：<a href="http://www.cnblogs.com/xiangxiaodong/p/4080546.html" target="_blank" rel="external">http://www.cnblogs.com/xiangxiaodong/p/4080546.html</a><br>部署后输入以下地址测试是否部署成功：<br>访问 Nginx：<a href="http://192.168.154.162/demos/" target="_blank" rel="external">http://192.168.154.162/demos/</a><br>直接访问 Tomcat：<a href="http://192.168.154.162:8080/demos/" target="_blank" rel="external">http://192.168.154.162:8080/demos/</a><br>5.1.1 war 包目录：<br>/home/hankchan101/demos<br>5.1.2 Tomcat 位置：<br>/usr/local/tomcat<br>5.1.3 配置 Tomcat 配置文件 server.xml<br>修改添加如下配置：<br>&lt;Host name=”localhost” unpackWARs=”true” autoDeploy=”false”<br>appBase=”/home/hankchan101/demos” deployOnStartup=”false”&gt;<br>&lt;Context path=”/demos” docBase=”webapp_deploy_demo.war” /&gt;<br>。。。<br>&lt;/Host&gt;<br>主要注意：<br>在 Host 节点中新增 Context 子节点；<br>Context 节点的 path 属性为上下文路径（对应请求 URL 的后缀），docBase 属性<br>为 war 包文件名；<br>Host 节点的 appBase 属性值为 war 包所在目录；<br>5.1.4 Nginx 配置：<br>upstream 配置：<br>upstream tomcat { # 配置 war 包部署方式到 tomcat 中<br>server localhost:8080 weight=10;<br>}<br>server 配置：<br>server {<br>listen 80;<br>server_name localhost;<br>index index.jsp index.htm index.html; # 配置 war 包部署方式到 tomcat 中<br>root /home/hankchan101; # 配置 war 包部署方式到 tomcat 中<br>location /demos { # 配置war包部署方式到tomcat中的location配置信息<br>proxy_pass <a href="http://tomcat" target="_blank" rel="external">http://tomcat</a>;<br>}<br>。。。<br>}<br>到此配置完成，在浏览器测试部署是否成功即可。<br>5.1.5 响应原理：<br>Nginx 配置文件中的 location 配置信息是：如果请求的路径为/demos，就通过反<br>向代理转到 <a href="http://tomcat。" target="_blank" rel="external">http://tomcat。</a> 根据该值 tomcat 找到 upstream 中指定的 server 地址，<br>也就是 localhost:8080。后面的就由该 Tomcat 服务器去处理请求：根据该 ip+port<br>的地址到了该tomcat server中的server.xml配置文件中Host节点的appBase属性<br>值可以找到 web 应用的 war 包。进而响应请求。<br>5.2 源码部署<br>（待更新。。）</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2016/10/01/linux-e9-83-a8-e7-bd-b2-e5-9f-ba-e4-ba-8e-nginx-tomcat-e7-9a-84-e7-9a-84-web-e5-ba-94-e7-94-a8/" data-id="cj359k3za0018fwv7ixzj13nr" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-mybatis-e7-bb-93-e5-90-88spring-e7-9a-84-e5-85-b6-e4-b8-ad-e4-b8-80-e7-a7-8d-e5-bc-80-e5-8f-91-e6-80-9d-e8-b7-af" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2016/09/23/mybatis-e7-bb-93-e5-90-88spring-e7-9a-84-e5-85-b6-e4-b8-ad-e4-b8-80-e7-a7-8d-e5-bc-80-e5-8f-91-e6-80-9d-e8-b7-af/" class="article-date">
  <time datetime="2016-09-23T13:01:59.000Z" itemprop="datePublished">2016-09-23</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Mybatis/">Mybatis</a>►<a class="article-category-link" href="/categories/Mybatis/Spring/">Spring</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2016/09/23/mybatis-e7-bb-93-e5-90-88spring-e7-9a-84-e5-85-b6-e4-b8-ad-e4-b8-80-e7-a7-8d-e5-bc-80-e5-8f-91-e6-80-9d-e8-b7-af/">Mybatis结合Spring的其中一种开发思路</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>在分布式、微服务化等概念越来越被广泛应用的今天，对于我等小小程序猿来说，可能还没有足够的实力去主导整体架构的设计开发。 但是在实际的开发过程中，也需要我们去思考，如果让自己的代码更加具有架构的思维，给后续的开发带来更多的益处呢？<br>对于我来说，目前能接触到的跟架构相关的东西无非就是服务化框架EDAS-HSF了，在这个架构的基础上，其实还是更多的需要跟Spring打交道。因为HSF也需要跟Spring配合来使用嘛！<br>因为项目的数据持久化主要是采用了Mybatis+OTS的解决方案，所以如何处理好这两个ORM框架跟Spring的关系当然就是我所需要认真思考的问题了！<br>这次要记下的主要也是我对Mybatis和Spring的一些开发的学习过程（OTS后续再看学习进展如何吧）<br>话不多说，先搞个代码片段（要知道这段代码放在抽象类中呀！）：</p>
<pre><code>{
    public static final String SUFFIX_MAPPER = &quot;Mapper&quot;;

    protected SqlSessionFactory sqlSessionFactory;

    protected DataSource dataSource;
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    protected TransactionFactory transactionFactory;
    public void setTransactionFactory(TransactionFactory transactionFactory) {
        this.transactionFactory = transactionFactory;
    }

    protected String config = &quot;mybatis.xml&quot;;
    public void setConfig(String config) {
        this.config = config;
    }

    abstract public void init();

    public void init(Class&amp;lt;?&amp;gt;... resultClasses) {
        if(dataSource != null) {
            if(transactionFactory == null) transactionFactory = new JdbcTransactionFactory();
            Environment environment = new Environment(&quot;current&quot;, transactionFactory, dataSource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(new Configuration(environment));
        } else {
            try (InputStream inputStream = Resources.getResourceAsStream(config)) {
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch(IOException x) {
                throw new RuntimeException(x);
            }
        }

        Configuration configuration = sqlSessionFactory.getConfiguration();
        for(Class&amp;lt;?&amp;gt; resultClass : resultClasses) {
            try {
                Class&amp;lt;?&amp;gt; mapperClass = Class.forName(resultClass.getName() + SUFFIX_MAPPER);
                configuration.addMapper(mapperClass);
            } catch(Exception x) {
                x.printStackTrace();
                throw new RuntimeException(x);
            }
        }
    }
}
`&lt;/pre&gt;
我是没打算解释太多这段代码的含义的，如果接触过Mybatis，我相信你看得懂！大概的作用就是支持你在开发的时候，可以不需要将Mybatis完全地跟Spring整合在一起，支持多个数据源的配置。
如果说你希望同时使用关系型数据库和非关系型数据库，这一点就显得比较有价值了。通过这段代码的处理，可以使得Mybatis使用原始的开发方式，而Spring可以通过配置其他的dataSource数据源。主要是我自己表达得不够清晰，我相信程序这东西，除了思考（带我的王工给我强调的最多的一句话就是：你不能上来就唰唰唰敲代码，要多思考！。。。），还是需要自己动手去实践的。
顺便怼一段配合上述代码的几个通用的数据库操作方法吧！其他的回头有空再更新！！要去踢球了！！
&lt;pre&gt;`public &amp;lt;T&amp;gt; T create_h(T object) { // success to INSERT （在openSession方法中形参boolean autoCommit为true）
        try(SqlSession session = sqlSessionFactory.openSession(true)) {
            int n = session.insert(object.getClass().getName() + SUFFIX_MAPPER + &quot;.create&quot;, object);
            return n &amp;gt; 0 ? object : null;
        }
    }
    public &amp;lt;T&amp;gt; void insert_h(T object) { // success to INSERT
        try(SqlSession session = sqlSessionFactory.openSession()) {
            session.selectOne(object.getClass().getName() + SUFFIX_MAPPER + &quot;.insert&quot;, object);
        }
    }
    public &amp;lt;T&amp;gt; int delete_h(Class&amp;lt;T&amp;gt; resultClass, Object param) { // success to DELETE （占位符参数直接使用实体类传参）
        try(SqlSession session = sqlSessionFactory.openSession(true)) {
            return session.delete(resultClass.getName() + SUFFIX_MAPPER + &quot;.delete&quot;, param);
        }
    }
    public &amp;lt;T&amp;gt; T update_h(T object) { // success to UPDATE 
        try(SqlSession session = sqlSessionFactory.openSession(true)) {
            int n = session.update(object.getClass().getName() + SUFFIX_MAPPER + &quot;.update&quot;, object);
            return n &amp;gt; 0 ? object : null;
        }
    }
    public &amp;lt;T&amp;gt; T get_h(String verb, Class&amp;lt;T&amp;gt; resultClass, Object param) { // success to GET T
        try(SqlSession session = sqlSessionFactory.openSession()) {
            return session.selectOne(resultClass.getName() + SUFFIX_MAPPER + &quot;.&quot; + verb, param);
        }
    }
    public &amp;lt;T&amp;gt; List&amp;lt;T&amp;gt; query_h(String verb, Class&amp;lt;T&amp;gt; resultClass, Object param) { // success to QUERY List
        try(SqlSession session = sqlSessionFactory.openSession()) {
            return session.selectList(resultClass.getName() + SUFFIX_MAPPER + &quot;.&quot; + verb, param);
        }
    }
</code></pre>
      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2016/09/23/mybatis-e7-bb-93-e5-90-88spring-e7-9a-84-e5-85-b6-e4-b8-ad-e4-b8-80-e7-a7-8d-e5-bc-80-e5-8f-91-e6-80-9d-e8-b7-af/" data-id="cj359k3zg001bfwv72cswaq6b" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-e5-85-b3-e4-ba-8e-e5-88-86-e5-b8-83-e5-bc-8f-e8-bd-ac-e8-bd-bd-e5-ad-a6-e4-b9-a0" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2016/09/02/e5-85-b3-e4-ba-8e-e5-88-86-e5-b8-83-e5-bc-8f-e8-bd-ac-e8-bd-bd-e5-ad-a6-e4-b9-a0/" class="article-date">
  <time datetime="2016-09-02T11:51:45.000Z" itemprop="datePublished">2016-09-02</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/分布式/">分布式</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2016/09/02/e5-85-b3-e4-ba-8e-e5-88-86-e5-b8-83-e5-bc-8f-e8-bd-ac-e8-bd-bd-e5-ad-a6-e4-b9-a0/">关于分布式(转载学习)</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>作者：陈叶皓（携程邮轮研发部软件架构师）</p>
<p>我的架构师同事问我：“为什么你总说要在服务层实现读写分离，我们已经在数据库实现了读写分离，是不是已经够用”。以下是我的解释</p>
<p>在做网站性能优化的时候，我常常忘记还有数据库读写分离这件事，因为数据库读写分离，对性能带来的提高太有限了，实际上，就是一倍（一台服务器变成两台服务器）。当你的网站业务发展，如果从无到有地使用数据库读写分离，提高了一倍的服务能力，你很快就需要想新的优化方案。实际上，数据库的读写分离，更像是数据安全的一个副产品，用一台数据库服务器不安全（怕数据丢失），用一台服务器作为备份，既然有了两台服务器，就充分利用吧，于是有了“读写分离”，提高一倍也是好的。</p>
<p>于是，能够十倍百倍提高性能的方案出现了，缓存加服务器集群，这是最常用且有效的提高网站访问量的设计。使用共享缓存（memcached，redis）可以获得十到几十倍的性能提升，使用进程内缓存，可以得到百倍的性能提升；集群中增加一倍的服务器，可以增加一倍的计算能力，服务更多的并发请求。等一下，上面所说的方案，其实只对“读”操作才有效，对“写”操作可以说是毫无用处。</p>
<p>那么有什么办法可以提高“写”操作的性能，在架构部署的设计方面，我的答案是，“没有”。</p>
<p>从硬件入手，可以使用SSD硬盘。愿意替换底层数据库，可以使用hbase或者cassandra，都不在今天讨论的范围。我想说的是，既然使用缓存和增加服务器，对于“写”操作没有优化作用，在一开始，“写”操作相关的服务，就不该和“读”操作一起，被分配到数量庞大的计算机集群里。</p>
<p>想象这样的架构设计，我有一个“读”服务的集群，一共4台服务器，我有一台“写”服务器（另一台备用，故障时切换）。当我的网站访问量上升，我增加“读”服务器集群到8台，简单就能应付问题。因为“读”服务是状态无关的，增加到100台也不会带来错误的数据，这是一个重要的思想，状态无关的服务，才可以放心地水平扩展，事实上，状态无关的服务，通常只有“读”服务。</p>
<p>那么当“写”服务撑不住的时候，怎么办，嗯。。。总会有办法，反正不是加缓存或者是使用集群，这个可以做架构师面试题。</p>
<p>然后我解释一下为什么不该在集群里面运行“写”服务，我把“写”服务分为两种。</p>
<ol>
<li>和“状态”（可能发生冲突的情形）弱相关，比如用户提供内容（UGC）的操作，每个用户提交自己的评论，或者发布自己的微博，不太容易发生冲突。对于这类“写”服务，部署在集群里面勉强可行，虽然没带来什么好处，但也没有引入错误<br>`</li>
<li><pre>`  和“状态”（可能发生冲突的情形）强相关，比如包含库存操作的电商网站，上千人“秒杀”热门商品，允许这样的操作在集群内并发，是架构师自己作死的节奏啊<br>明白了这个道理，你就知道我之前为什么说是“一台”写服务器，只有一台服务器，才可以保证在“秒杀”场景下，不会在没有库存的情况下继续售卖成功。</pre></li>
</ol>
<p>细心的读者（嗯，就是你）会继续追问，在一台服务器的情况下，现在都是多核并发编程，保证串行操作也不是容易的事啊。问得太好了，我这大半年写的系列文章，都是为了解决这个问题，你需要的是actor模型。异步编程加上进程内的消息队列，可以高效地对并发操作进行串行的处理。</p>
<p>结论，使用服务器集群提高性能只对“读”服务有效，对“写”服务无效，“写”服务器应该使用主/从模式，同一时间只使用一台服务器。在“写”服务器内部，使用支持actor模型的编程语言，保证关键操作的串行。最后老生常谈，支持actor模型的编程语言是：Erlang，Go，Scala，F#</p>
<p>是不是要在标题的“作用”之前加上“重要”两个字，我犹豫了一下，zookeeper提供的功能是如此的重要，以至于如果你在应用中不使用它，早晚也会在你的应用中去实现zookeeper 的功能，所以，zookeeper值得你花（一点）时间去掌握。</p>
<p>zookeeper是为了“分布式”而诞生的，我反复在说“分布式”，并不是赶潮流，而是被潮流推着向前。在任何互联网生产应用中，哪怕你的公司规模小，访问量用一台服务器足够应付，仍然不能容忍当服务器故障时，没有备用的服务器可切换，这个称为“防止单点故障”，因为你至少要用两台服务器来防止单点故障，所以你已经在“分布式”的服务环境里。</p>
<p>我们来回顾上一篇提到的“为什么要在服务层设计读写分离”，我把应用层的通用服务分为“读”服务和“写”服务，“读”服务用集群来实现高可用高性能，而“写”服务用单台服务器来保证事务顺序执行。</p>
<p>那么，“单台服务器”听上去好危险的样子，于是今天的主角登场，我们需要zookeeper。</p>
<p>你也许听到过，这种应用场景叫做master/slave，或者我更喜欢称为主/备模式，在这种场景下，我有两台服务器（主和备），任何情况下，只有“主”在工作，“备”是在主出现故障时，接替“主”来提供服务。在zookeeper的支持下，这一过程是这样实现的， Zookeeper提供目录和节点的服务，当我的两台服务器启动时，会在zookeeper的指定目录下创建对应自己的临时节点（这个过程称为“注册”），所谓临时节点，是靠心跳（定时向zookeeper服务器发送数据包）维系，当服务器出现故障（无法向zookeeper服务器发送数据包），zookeeper会删除临时节点。服务器向zookeeper注册时，zookeeper会分配序列号，我们认为序列号小的那个，就是“主”，序列号大的那个，就是“备”。</p>
<p>当我们的客户端（通常是web server）需要访问“写”服务时，需要连接zookeeper，获得指定目录下的临时节点列表，也就是已经注册的服务器信息，获得序列号小的那台“主”服务器的地址，进行后续的访问操作。以达到“总是访问主服务器”的目的。</p>
<p>当“主”服务器发生故障，zookeeper从指定目录下删除对应的临时节点，同时可以通知关心这一变化的所有客户端，高效且迅速的传播这一信息，你想一想，如果不是使用zookeeper，要自己实现这个功能，可没。。。那么简单（不许唱！）</p>
<p>我们为了消除单点故障而使用的主/备模式依赖zookeeper，那么zookeeper可不能有单点故障，所以zookeeper在诞生的时候，就是用集群的模式工作，用多台服务器来消除自身的单点故障隐患，怎么样，无可挑剔吧。</p>
<p>总结，在多核并行计算模式下，我认定基于消息传递的actor模型（源自erlang）是正确的编程方式，在actor模型下，可以简单实现基于服务层的串行操作，保证“写”操作的完整和一致。使用actor模型，需要用主/备的部署架构来消除单点故障，实现主/备的部署架构，最简单可靠的方法是使用zookeeper。所以我现在的软件架构是这么推导出来的高并发需求 -&gt; 异步计算 (使用actor model) -&gt; master/slave (使用zookeeper)</p>
<p>我们先来回顾一下之前提到过的知识点， 1.在一台电脑上，使用异步编程可以提高cpu的使用效率 2.使用Actor模型，实现同一台电脑上，在并发环境下的串行操作，保证事务执行的正确 3.在多服务器环境下，actor模型配合Zookeeper，可以实现在多服务器环境下的串行操作，保证事务执行正确 4.对应用进行读写分离的设计，做到“写服务”（有状态）执行正确，同时又能方便地（增加服务器）提高“读服务”（无状态）的性能</p>
<p>所以，在服务实现的内部，异步已经无处不在，今天我来讲服务的消费者，也就是浏览器客户端，为什么也需要使用异步模式。</p>
<p>在Joe Armstrong创造erlang的时候，他假设网络通讯是不可靠的，外部服务的响应时间是不可靠的，所以erlang里面只有异步调用，同步调用是用异步来模拟。Erlang软件的运行，不会因为任何的外部操作而阻塞cpu。</p>
<p>而到了互联网时代，Joe Armstrong的假设依旧成立，网络通讯不可靠，外部服务不可靠。设计同步调用的应用架构，从一开始就引入了巨大的风险。举个简单的电商下单的例子，步骤如下， 1.浏览器发送下单请求到网页服务器（web server） 2.网页服务器发送请求到应用服务器 3.应用服务器检查库存，锁定库存，生成订单。通知网页服务器下单成功 4.网页服务器把下单结果传送到用户的浏览器</p>
<p>在这个场景下，步骤3最为耗时，有时需要超过1秒。对于步骤2，我们有很多方法来实现异步操作，如果不能实现，那网页服务器被应用服务器的响应所阻塞，吞吐量将急剧下降。</p>
<p>今天我们主要讨论步骤1，用户在下单，期望看到下单的结果，这是一个典型的同步操作，随着并发量上升，服务器的响应时间可能会超过30秒，最终造成浏览器的超时，用户什么也看不到，这是最坏的结果。我们来把这个同步调用，用异步来模拟， 1.浏览器发送下单请求到网页服务器（web server） 2.网页服务器发送请求到应用服务器 3.a)应用服务器生成订单号，把订单号返回网页服务器 b)应用服务器检查库存，锁定库存，把订单状态改成“下单成功” 4.浏览器收到订单号，采用ajax方式，每隔2秒，请求网页服务器，查询订单状态，直到获得“下单成功”的状态，跳转到下单成功页面</p>
<p>在这个异步流程中，步骤3.b和步骤4在时间上是并行执行的，但是，还记得我们的“读写分离”设计吗，步骤3访问的是“写”服务，步骤4访问的是“读”服务，这两个服务可以独立优化，不会成为对方的瓶颈。</p>
<p>使用浏览器的异步访问还带来额外的好处，在异步架构下，网页服务器和应用服务器的响应都非常迅速。原先的耗时请求“请帮我下单，并告诉我下单结果”被拆分成不耗时的请求“帮我下单”（一次）和“刚才的下单成功了吗”（一次或多次）。</p>
<p>如果使用了老式的网页服务器，不支持异步请求应用服务器，这样还能一举解决网页服务器访问应用服务器阻塞的问题。</p>
<p>在互联网高并发的状态改变（写）操作，把业务流程设计成异步是上策，比如大众点评的退款操作，系统会告诉用户“已收到您的退款请求，会在x个工作日能把钱退到您的账户”，这样的异步业务流程，对于系统毫无压力。有时出于用户体验不得不采用同步流程，比如上面说的下单流程，如果一开始设计成用异步流程来模拟，有两个好处 1.不会因为并发量突然上升而触发浏览器超时 2.可以对“读”，“写”服务独立优化 另外，在ajax异步操作时，还可以在浏览器绘制动画来安抚用户焦躁的情绪，不要说我没告诉你~</p>
<p>用异步流程模拟同步流程的补充说明，在浏览器内使用ajax轮询下单结果，是不得以。在服务器可以主动访问客户端的环境下，应该总是优先考虑回调的方式。浏览器是特殊的环境，服务器访问浏览器有时有困难，所以才使用比较保险的轮询模式。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2016/09/02/e5-85-b3-e4-ba-8e-e5-88-86-e5-b8-83-e5-bc-8f-e8-bd-ac-e8-bd-bd-e5-ad-a6-e4-b9-a0/" data-id="cj359k3uj0003fwv7044ygxrd" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-e9-80-9a-e8-bf-87-e6-90-ad-e5-bb-ba-e6-9c-ac-e5-9c-b0-e6-9c-8d-e5-8a-a1-e5-99-a8-e8-8e-b7-e5-8f-96webstorm-e7-9a-84-e4-bd-bf-e7-94-a8-e6-9d-83-e9-99-90" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2016/08/16/e9-80-9a-e8-bf-87-e6-90-ad-e5-bb-ba-e6-9c-ac-e5-9c-b0-e6-9c-8d-e5-8a-a1-e5-99-a8-e8-8e-b7-e5-8f-96webstorm-e7-9a-84-e4-bd-bf-e7-94-a8-e6-9d-83-e9-99-90/" class="article-date">
  <time datetime="2016-08-16T04:12:26.000Z" itemprop="datePublished">2016-08-16</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/未分类/">未分类</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2016/08/16/e9-80-9a-e8-bf-87-e6-90-ad-e5-bb-ba-e6-9c-ac-e5-9c-b0-e6-9c-8d-e5-8a-a1-e5-99-a8-e8-8e-b7-e5-8f-96webstorm-e7-9a-84-e4-bd-bf-e7-94-a8-e6-9d-83-e9-99-90/">通过搭建本地服务器获取Webstorm的使用权限</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <ol>
<li>下载最新版（2016.2亲测可用）Webstorm</li>
<li>将webstormInWindows.zip下载解压（见<a href="https://github.com/hank0chan/blog/blob/master/201608），运行其中一个相应的exe文件" target="_blank" rel="external">https://github.com/hank0chan/blog/blob/master/201608），运行其中一个相应的exe文件</a></li>
<li>在Webstorm中选择输入Server注册</li>
<li>输入<a href="http://127.0.0.1:41017/" target="_blank" rel="external">http://127.0.0.1:41017</a></li>
<li>成功！！ btw:（这只是Windows版本的zip包，其他操作系统的zip文件如若尚未上传，可联系本人取得）</li>
</ol>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2016/08/16/e9-80-9a-e8-bf-87-e6-90-ad-e5-bb-ba-e6-9c-ac-e5-9c-b0-e6-9c-8d-e5-8a-a1-e5-99-a8-e8-8e-b7-e5-8f-96webstorm-e7-9a-84-e4-bd-bf-e7-94-a8-e6-9d-83-e9-99-90/" data-id="cj359k3ye000wfwv70q9nc3pb" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-e5-b0-81-e8-a3-85-e4-bd-bf-e7-94-a8completionservice-e5-b9-b6-e5-8f-91-e5-a4-84-e7-90-86-e4-bb-bb-e5-8a-a1" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2016/08/09/e5-b0-81-e8-a3-85-e4-bd-bf-e7-94-a8completionservice-e5-b9-b6-e5-8f-91-e5-a4-84-e7-90-86-e4-bb-bb-e5-8a-a1/" class="article-date">
  <time datetime="2016-08-09T09:57:38.000Z" itemprop="datePublished">2016-08-09</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/未分类/">未分类</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2016/08/09/e5-b0-81-e8-a3-85-e4-bd-bf-e7-94-a8completionservice-e5-b9-b6-e5-8f-91-e5-a4-84-e7-90-86-e4-bb-bb-e5-8a-a1/">封装使用CompletionService并发处理任务</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>在Java JDK中，CompletionService是可以用于处理多任务的API。其实现类是ExecutorCompletionService。 在该接口中，submit() 相当于生产者处理任务，在每次处理完成后可以使用take() 取得Future，再由调用get() 方法返回任务结果。 从源码中可以知道，submit() 方法的入参为Callable 和Runnable，就是说任务需要实现这两个接口之一。</p>
<pre><code>&amp;lt;T&amp;gt; Future&amp;lt;T&amp;gt; submit(Callable&amp;lt;T&amp;gt; task);
Future&amp;lt;?&amp;gt; submit(Runnable task);
`&lt;/pre&gt;
我们考虑：在应用过程中，对任务的处理，可以抽象为给定输入的处理任务，经过中间计算过程，最后返回输出结果。 如此，我们可以定义一个接口如下：

public interface SliceableTask extends Iterable {
&lt;pre&gt;`public MID compute(IN input);

public void gather(TaskSlice&amp;lt;IN, M&amp;gt; slice);

public OUT success();

public void fail();
`&lt;/pre&gt;
}

其中为了保证输入参数由于各个CPU计算速度不同返回的数据位置能够和输入时相同，需要实现Iterable接口并在后期重写iterator() 方法。 其中的TaskSlice是作为处理排序的一个类，实现了Comparable接口，如下：

public class TaskSlice implements Comparable&amp;gt; {
&lt;pre&gt;`int index;
int from, to;

T input;
R output;

// Getter and Setter methods... toString method...

@Override
public int compareTo(TaskSlice&amp;lt;IN, M&amp;gt; o) {
    return new Integer(index).compareTo(o.index);
}
`&lt;/pre&gt;
}

接下来，封装CompletionService成为一个提供服务的类 SlicingExecutor 。（代码在同目录下） 此外，我们可以创建一个 SliceableTask 的抽象实现类 SilceableTaskImpl，在其中只重写gather方法用于处理通用的任务排序等，如下：

public abstract class SilceableTaskV1d0 implements SliceableTask {
&lt;pre&gt;`protected List&amp;lt;TaskSlice&amp;lt;IN, M&amp;gt;&amp;gt; slices;

@Override
public void gather(TaskSlice&amp;lt;IN, M&amp;gt; slice) {
    if(slices == null) {
        slices = new ArrayList&amp;lt;TaskSlice&amp;lt;IN, M&amp;gt;&amp;gt;();
    }
    slices.add(slice);
    Collections.sort(slices);
}

@Override
public void fail() {}
</code></pre><p>}</p>
<p>到此，就可以使用自己封装的并发任务处理方法去做并发处理，但是对于不同特点的问题和任务，需要谨慎选择不同的实现。 本着学习分享的态度，如若还有优化的解决方法，望指教一二！</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2016/08/09/e5-b0-81-e8-a3-85-e4-bd-bf-e7-94-a8completionservice-e5-b9-b6-e5-8f-91-e5-a4-84-e7-90-86-e4-bb-bb-e5-8a-a1/" data-id="cj359k3x1000gfwv7ea566mf5" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  


  <nav id="page-nav">
    <span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><a class="page-number" href="/page/3/">3</a><a class="extend next" rel="next" href="/page/2/">__('next') &raquo;</a>
  </nav>
</section>
        
          <aside id="sidebar">
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">分类</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Docker/">Docker</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/">Eclipse</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/Mybatis/">Mybatis</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/Spring/">Spring</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/分布式/">分布式</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Git/">Git</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Linux/">Linux</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Linux/Nginx/">Nginx</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Mybatis/">Mybatis</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Mybatis/Spring/">Spring</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Mybatis/Spring/SpringMVC/">SpringMVC</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Mybatis/SpringMVC/">SpringMVC</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Nginx/">Nginx</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Spring/">Spring</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/SpringMVC/">SpringMVC</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/分布式/">分布式</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/日志管理/">日志管理</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/未分类/">未分类</a></li></ul>
    </div>
  </div>


  
    

  
    
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/03/">三月 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/01/">一月 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/10/">十月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/09/">九月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/08/">八月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/07/">七月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/06/">六月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/05/">五月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/04/">四月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/03/">三月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/02/">二月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/01/">一月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/12/">十二月 2015</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2017/03/13/docker-e5-be-ae-e6-9c-8d-e5-8a-a1-e5-ae-b9-e5-99-a8-e5-8c-96-e5-bc-80-e5-8f-91-e6-8e-a2-e7-b4-a2/">Docker-微服务容器化开发探索</a>
          </li>
        
          <li>
            <a href="/2017/01/20/drds-e7-bb-93-e5-90-88mybatis-e8-a7-a3-e5-86-b3-e6-96-b9-e6-a1-88-e4-b8-8e-e8-a7-84-e8-8c-83/">DRDS结合Mybatis解决方案与规范</a>
          </li>
        
          <li>
            <a href="/2017/01/03/springmvc-thymeleaf-e5-bc-80-e5-8f-91-e6-8c-87-e5-8d-97-demo/">SpringMVC-Thymeleaf-开发指南-Demo</a>
          </li>
        
          <li>
            <a href="/2016/10/16/nginx-e4-bb-a3-e7-90-86-e6-9c-8d-e5-8a-a1-e5-99-a8-e6-96-b9-e5-bc-8f-e8-a7-a3-e5-86-b3-e5-ae-9e-e9-99-85-e5-bc-80-e5-8f-91-e4-b8-adajax-e8-af-b7-e6-b1-82-e7-9a-84-e8-b7-a8-e5-9f-9f-e9-97-ae-e9-a2-98/">Nginx代理服务器方式解决实际开发中Ajax请求的跨域问题</a>
          </li>
        
          <li>
            <a href="/2016/10/11/e5-b7-a5-e5-8e-82-e6-a8-a1-e5-bc-8f-e5-92-8c-e5-8d-95-e4-be-8b-e6-a8-a1-e5-bc-8f-e7-bb-93-e5-90-88-e5-ba-94-e7-94-a8-e7-9a-84-e5-9c-ba-e6-99-af-e6-80-9d-e8-80-83/">工厂模式和单例模式结合应用的场景思考</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      &copy; 2017 Hank Chan<br>
      Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>
    </div>
    <nav id="mobile-nav">
  
    <a href="/" class="mobile-nav-link">Home</a>
  
    <a href="/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>


  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>

  </div>
</body>
</html>